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
)
60 set_belongs_to_association_for(record
)
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})"
86 when @reflection.options
[:finder_sql]
87 @finder_sql = interpolate_sql(@reflection.options
[:finder_sql])
89 when @reflection.options
[:as]
91 "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
92 "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
93 @finder_sql << " AND (#{conditions})" if conditions
96 @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
97 @finder_sql << " AND (#{conditions})" if conditions
100 if @reflection.options
[:counter_sql]
101 @counter_sql = interpolate_sql(@reflection.options
[:counter_sql])
102 elsif @reflection.options
[:finder_sql]
103 # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
104 @reflection.options
[:counter_sql] = @reflection.options
[:finder_sql].sub(/SELECT (\/\
*.*?\
*\
/ )?(.*)\bFROM\b/im
) { "SELECT #{$1}COUNT(*) FROM" }
105 @counter_sql = interpolate_sql(@reflection.options
[:counter_sql])
107 @counter_sql = @finder_sql
113 set_belongs_to_association_for(create_scoping
)
115 :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options
[:order], :limit => @reflection.options
[:limit], :include => @reflection.options
[:include]},
116 :create => create_scoping