1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
module ActiveRecord
# = Active Record Has Many Association
module Associations
# This is the proxy that handles a has many association.
#
# If the association has a <tt>:through</tt> option further specialization
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < AssociationCollection #:nodoc:
protected
def insert_record(record, force = false, validate = true)
set_owner_attributes(record)
save_record(record, force, validate)
end
private
# Returns the number of records in this collection.
#
# If the association has a counter cache it gets that value. Otherwise
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
# there's one. Some configuration options like :group make it impossible
# to do an SQL count, in those cases the array count will be used.
#
# That does not depend on whether the collection has already been loaded
# or not. The +size+ method is the one that takes the loaded flag into
# account and delegates to +count_records+ if needed.
#
# If the collection is empty the target is set to an empty array and
# the loaded flag is set to true as well.
def count_records
count = if has_cached_counter?
@owner.send(:read_attribute, cached_counter_attribute_name)
elsif @reflection.options[:counter_sql] || @reflection.options[:finder_sql]
@reflection.klass.count_by_sql(custom_counter_sql)
else
scoped.count
end
# If there's nothing in the database and @target has no new records
# we are certain the current target is an empty array. This is a
# documented side-effect of the method that may avoid an extra SELECT.
@target ||= [] and loaded! if count == 0
[@reflection.options[:limit], count].compact.min
end
def has_cached_counter?(reflection = @reflection)
@owner.attribute_present?(cached_counter_attribute_name(reflection))
end
def cached_counter_attribute_name(reflection = @reflection)
"#{reflection.name}_count"
end
def update_counter(difference, reflection = @reflection)
if has_cached_counter?(reflection)
counter = cached_counter_attribute_name(reflection)
@owner.class.update_counters(@owner.id, counter => difference)
@owner[counter] += difference
@owner.changed_attributes.delete(counter) # eww
end
end
# This shit is nasty. We need to avoid the following situation:
#
# * An associated record is deleted via record.destroy
# * Hence the callbacks run, and they find a belongs_to on the record with a
# :counter_cache options which points back at our @owner. So they update the
# counter cache.
# * In which case, we must make sure to *not* update the counter cache, or else
# it will be decremented twice.
#
# Hence this method.
def inverse_updates_counter_cache?(reflection = @reflection)
counter_name = cached_counter_attribute_name(reflection)
reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
inverse_reflection.counter_cache_column == counter_name
}
end
# Deletes the records according to the <tt>:dependent</tt> option.
def delete_records(records, method = @reflection.options[:dependent])
if method == :destroy
records.each { |r| r.destroy }
update_counter(-records.length) unless inverse_updates_counter_cache?
else
keys = records.map { |r| r[@reflection.association_primary_key] }
scope = scoped.where(@reflection.association_primary_key => keys)
if method == :delete_all
update_counter(-scope.delete_all)
else
update_counter(-scope.update_all(@reflection.foreign_key => nil))
end
end
end
alias creation_attributes construct_owner_attributes
end
end
end
|