3 class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
4 def initialize(owner, reflection)
9 def create(attributes = {})
10 create_record(attributes) { |record| insert_record(record) }
13 def create!(attributes = {})
14 create_record(attributes) { |record| insert_record(record, true) }
18 @reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
21 def reset_column_information
22 @reflection.reset_column_information
26 return @has_primary_key unless @has_primary_key.nil?
27 @has_primary_key = (@owner.connection.supports_primary_key? &&
28 @owner.connection.primary_key(@reflection.options[:join_table]))
32 def construct_find_options!(options)
33 options[:joins] = @join_sql
34 options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
35 options[:select] ||= (@reflection.options[:select] || '*')
42 def insert_record(record, force = true, validate = true)
44 raise ActiveRecord::ConfigurationError,
45 "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
52 return false unless record.save(validate)
56 if @reflection.options[:insert_sql]
57 @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
59 attributes = columns.inject({}) do |attrs, column|
61 when @reflection.primary_key_name.to_s
62 attrs[column.name] = owner_quoted_id
63 when @reflection.association_foreign_key.to_s
64 attrs[column.name] = record.quoted_id
66 if record.has_attribute?(column.name)
67 value = @owner.send(:quote_value, record[column.name], column)
68 attrs[column.name] = value unless value.nil?
75 "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
76 "VALUES (#{attributes.values.join(', ')})"
78 @owner.connection.insert(sql)
84 def delete_records(records)
85 if sql = @reflection.options[:delete_sql]
86 records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
88 ids = quoted_record_ids(records)
89 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})"
90 @owner.connection.delete(sql)
95 if @reflection.options[:finder_sql]
96 @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
98 @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
99 @finder_sql << " AND (#{conditions})" if conditions
102 @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}"
104 if @reflection.options[:counter_sql]
105 @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
106 elsif @reflection.options[:finder_sql]
107 # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
108 @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
109 @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
111 @counter_sql = @finder_sql
116 { :find => { :conditions => @finder_sql,
119 :order => @reflection.options[:order],
120 :include => @reflection.options[:include],
121 :limit => @reflection.options[:limit] } }
124 # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
125 # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
126 # an id column. This will then overwrite the id column of the records coming back.
127 def finding_with_ambiguous_select?(select_clause)
128 !select_clause && columns.size != 2
132 def create_record(attributes, &block)
133 # Can't use Base.create because the foreign key may be a protected attribute.
134 ensure_owner_is_not_new
135 if attributes.is_a?(Array)
136 attributes.collect { |attr| create(attr) }
138 build_record(attributes, &block)