3
# This is the proxy that handles a has many association.
5
# If the association has a <tt>:through</tt> option further specialization
6
# is provided by its child HasManyThroughAssociation.
7
class HasManyAssociation < AssociationCollection #:nodoc:
10
if @reflection.options[:primary_key]
11
quote_value(@owner.send(@reflection.options[:primary_key]))
17
# Returns the number of records in this collection.
19
# If the association has a counter cache it gets that value. Otherwise
20
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
21
# there's one. Some configuration options like :group make it impossible
22
# to do a SQL count, in those cases the array count will be used.
24
# That does not depend on whether the collection has already been loaded
25
# or not. The +size+ method is the one that takes the loaded flag into
26
# account and delegates to +count_records+ if needed.
28
# If the collection is empty the target is set to an empty array and
29
# the loaded flag is set to true as well.
31
count = if has_cached_counter?
32
@owner.send(:read_attribute, cached_counter_attribute_name)
33
elsif @reflection.options[:counter_sql]
34
@reflection.klass.count_by_sql(@counter_sql)
36
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
39
# If there's nothing in the database and @target has no new records
40
# we are certain the current target is an empty array. This is a
41
# documented side-effect of the method that may avoid an extra SELECT.
42
@target ||= [] and loaded if count == 0
44
if @reflection.options[:limit]
45
count = [ @reflection.options[:limit], count ].min
51
def has_cached_counter?
52
@owner.attribute_present?(cached_counter_attribute_name)
55
def cached_counter_attribute_name
56
"#{@reflection.name}_count"
59
def insert_record(record, force = false, validate = true)
60
set_belongs_to_association_for(record)
61
force ? record.save! : record.save(validate)
64
# Deletes the records according to the <tt>:dependent</tt> option.
65
def delete_records(records)
66
case @reflection.options[:dependent]
68
records.each { |r| r.destroy }
70
@reflection.klass.delete(records.map { |record| record.id })
72
ids = quoted_record_ids(records)
73
@reflection.klass.update_all(
74
"#{@reflection.primary_key_name} = NULL",
75
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
77
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
87
when @reflection.options[:finder_sql]
88
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
90
when @reflection.options[:as]
92
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
93
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
94
@finder_sql << " AND (#{conditions})" if conditions
97
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
98
@finder_sql << " AND (#{conditions})" if conditions
101
if @reflection.options[:counter_sql]
102
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
103
elsif @reflection.options[:finder_sql]
104
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
105
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
106
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
108
@counter_sql = @finder_sql
114
set_belongs_to_association_for(create_scoping)
116
:find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
117
:create => create_scoping