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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
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 < CollectionAssociation #:nodoc:
include ForeignAssociation
def handle_dependency
case options[:dependent]
when :restrict_with_exception
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
when :restrict_with_error
unless empty?
record = owner.class.human_attribute_name(reflection.name).downcase
message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.many', record: record, raise: true) rescue nil
if message
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
The error key `:'restrict_dependent_destroy.many'` has been deprecated and will be removed in Rails 5.1.
Please use `:'restrict_dependent_destroy.has_many'` instead.
MESSAGE
end
owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record)
throw(:abort)
end
else
if options[:dependent] == :destroy
# No point in executing the counter update since we're going to destroy the parent anyway
load_target.each { |t| t.destroyed_by_association = reflection }
destroy_all
else
delete_all
end
end
end
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
set_inverse_instance(record)
if raise
record.save!(:validate => validate)
else
record.save(:validate => validate)
end
end
def empty?
if reflection.has_cached_counter?
size.zero?
else
super
end
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 reflection.has_cached_counter?
owner._read_attribute reflection.counter_cache_column
else
scope.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
[association_scope.limit_value, count].compact.min
end
def update_counter(difference, reflection = reflection())
update_counter_in_database(difference, reflection)
update_counter_in_memory(difference, reflection)
end
def update_counter_in_database(difference, reflection = reflection())
if reflection.has_cached_counter?
owner.class.update_counters(owner.id, reflection.counter_cache_column => difference)
end
end
def update_counter_in_memory(difference, reflection = reflection())
if reflection.counter_must_be_updated_by_has_many?
counter = reflection.counter_cache_column
owner[counter] ||= 0
owner[counter] += difference
owner.send(:clear_attribute_change, counter) # eww
end
end
def delete_count(method, scope)
if method == :delete_all
scope.delete_all
else
scope.update_all(reflection.foreign_key => nil)
end
end
def delete_or_nullify_all_records(method)
count = delete_count(method, self.scope)
update_counter(-count)
end
# Deletes the records according to the <tt>:dependent</tt> option.
def delete_records(records, method)
if method == :destroy
records.each(&:destroy!)
update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
else
scope = self.scope.where(reflection.klass.primary_key => records)
update_counter(-delete_count(method, scope))
end
end
def concat_records(records, *)
update_counter_if_success(super, records.length)
end
def _create_record(attributes, *)
if attributes.is_a?(Array)
super
else
update_counter_if_success(super, 1)
end
end
def update_counter_if_success(saved_successfully, difference)
if saved_successfully
update_counter_in_memory(difference)
end
saved_successfully
end
end
end
end
|