Froze rails gems
[depot.git] / vendor / rails / activerecord / lib / active_record / associations / has_and_belongs_to_many_association.rb
1 module ActiveRecord
2 module Associations
3 class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
4 def create(attributes = {})
5 create_record(attributes) { |record| insert_record(record) }
6 end
7
8 def create!(attributes = {})
9 create_record(attributes) { |record| insert_record(record, true) }
10 end
11
12 protected
13 def construct_find_options!(options)
14 options[:joins] = @join_sql
15 options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
16 options[:select] ||= (@reflection.options[:select] || '*')
17 end
18
19 def count_records
20 load_target.size
21 end
22
23 def insert_record(record, force=true)
24 if record.new_record?
25 if force
26 record.save!
27 else
28 return false unless record.save
29 end
30 end
31
32 if @reflection.options[:insert_sql]
33 @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
34 else
35 columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
36
37 attributes = columns.inject({}) do |attrs, column|
38 case column.name.to_s
39 when @reflection.primary_key_name.to_s
40 attrs[column.name] = owner_quoted_id
41 when @reflection.association_foreign_key.to_s
42 attrs[column.name] = record.quoted_id
43 else
44 if record.has_attribute?(column.name)
45 value = @owner.send(:quote_value, record[column.name], column)
46 attrs[column.name] = value unless value.nil?
47 end
48 end
49 attrs
50 end
51
52 sql =
53 "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
54 "VALUES (#{attributes.values.join(', ')})"
55
56 @owner.connection.insert(sql)
57 end
58
59 return true
60 end
61
62 def delete_records(records)
63 if sql = @reflection.options[:delete_sql]
64 records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
65 else
66 ids = quoted_record_ids(records)
67 sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
68 @owner.connection.delete(sql)
69 end
70 end
71
72 def construct_sql
73 if @reflection.options[:finder_sql]
74 @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
75 else
76 @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
77 @finder_sql << " AND (#{conditions})" if conditions
78 end
79
80 @join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
81
82 if @reflection.options[:counter_sql]
83 @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
84 elsif @reflection.options[:finder_sql]
85 # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
86 @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
87 @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
88 else
89 @counter_sql = @finder_sql
90 end
91 end
92
93 def construct_scope
94 { :find => { :conditions => @finder_sql,
95 :joins => @join_sql,
96 :readonly => false,
97 :order => @reflection.options[:order],
98 :include => @reflection.options[:include],
99 :limit => @reflection.options[:limit] } }
100 end
101
102 # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
103 # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
104 # an id column. This will then overwrite the id column of the records coming back.
105 def finding_with_ambiguous_select?(select_clause)
106 !select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
107 end
108
109 private
110 def create_record(attributes, &block)
111 # Can't use Base.create because the foreign key may be a protected attribute.
112 ensure_owner_is_not_new
113 if attributes.is_a?(Array)
114 attributes.collect { |attr| create(attr) }
115 else
116 build_record(attributes, &block)
117 end
118 end
119 end
120 end
121 end