Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 204 lines (162 sloc) 5.826 kB
6abda69 @dhh Added preliminary support for join models [DHH] Added preliminary sup…
dhh authored
1 module ActiveRecord
fde9504 @rizwanreza Adds title to activerecord/lib/active_record/associations/*
rizwanreza authored
2 # = Active Record Has Many Through Association
6abda69 @dhh Added preliminary support for join models [DHH] Added preliminary sup…
dhh authored
3 module Associations
f6b12c1 @lifo Refactor HasManyThroughAssociation to inherit from HasManyAssociation…
lifo authored
4 class HasManyThroughAssociation < HasManyAssociation #:nodoc:
b049837 @jonleighton Add a HasAssociation module for common code for has_* associations
jonleighton authored
5 include ThroughAssociation
edc176d @jonleighton Make sure nested through associations are read only
jonleighton authored
6
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
7 def initialize(owner, reflection)
8 super
567d454 @jonleighton Memoize through association
jonleighton authored
9
10 @through_records = {}
11 @through_association = nil
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
12 end
13
1644663 @jonleighton Split AssociationProxy into an Association class (and subclasses) whi…
jonleighton authored
14 def concat(*records)
1d85a73 @jonleighton Associations - where possible, call attributes methods rather than di…
jonleighton authored
15 unless owner.new_record?
7ce7ae0 @jonleighton Get rid of AssociationCollection#save_record
jonleighton authored
16 records.flatten.each do |record|
53b76c1 @wangjohn Adding a bang to method name of raise_on_type_mismatch.
wangjohn authored
17 raise_on_type_mismatch!(record)
7ce7ae0 @jonleighton Get rid of AssociationCollection#save_record
jonleighton authored
18 end
19 end
20
21 super
d51e7f8 @technoweenie Ensure that has_many :through associations use a count query instead …
technoweenie authored
22 end
8c3b832 @miloops Use ARel in SQL generation through associations
miloops authored
23
610b632 @ernie Fix collection= on hm:t join models when unsaved
ernie authored
24 def concat_records(records)
25 ensure_not_nested
26
b1656fa @tenderlove let `insert_record` actuall save the object.
tenderlove authored
27 records = super(records, true)
610b632 @ernie Fix collection= on hm:t join models when unsaved
ernie authored
28
29 if owner.new_record? && records
30 records.flatten.each do |record|
31 build_through_record(record)
32 end
33 end
34
35 records
36 end
37
dfaad4f @jonleighton Only save the record once when calling create! on a collection associ…
jonleighton authored
38 def insert_record(record, validate = true, raise = false)
735844d @jonleighton Merge branch 'master' into nested_has_many_through
jonleighton authored
39 ensure_not_nested
dfaad4f @jonleighton Only save the record once when calling create! on a collection associ…
jonleighton authored
40
d849f42 @sgrif Autosave existing records on HMT associations when the parent is new
sgrif authored
41 if raise
42 record.save!(:validate => validate)
43 else
44 return unless record.save(:validate => validate)
dfaad4f @jonleighton Only save the record once when calling create! on a collection associ…
jonleighton authored
45 end
735844d @jonleighton Merge branch 'master' into nested_has_many_through
jonleighton authored
46
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
47 save_through_record(record)
8c52480 @chancancode Avoid using heredoc for user warnings
chancancode authored
48
1644663 @jonleighton Split AssociationProxy into an Association class (and subclasses) whi…
jonleighton authored
49 record
50 end
f6b12c1 @lifo Refactor HasManyThroughAssociation to inherit from HasManyAssociation…
lifo authored
51
15601c5 @jonleighton Let's be less blasé about method visibility on association proxies
jonleighton authored
52 private
53
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
54 def through_association
567d454 @jonleighton Memoize through association
jonleighton authored
55 @through_association ||= owner.association(through_reflection.name)
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
56 end
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
57
22f2518 @jonatack Follow-up to #14990 [ci skip]
jonatack authored
58 # The through record (built with build_record) is temporarily cached
59 # so that it may be reused if insert_record is subsequently called.
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
60 #
22f2518 @jonatack Follow-up to #14990 [ci skip]
jonatack authored
61 # However, after insert_record has been called, the cache is cleared in
62 # order to allow multiple instances of the same record in an association.
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
63 def build_through_record(record)
64 @through_records[record.object_id] ||= begin
c9c7ee7 @rafaelfranca Not need to pass join attributes to association build
rafaelfranca authored
65 ensure_mutable
66
7154899 @zzak Open extension point for defining options in build_through_record
zzak authored
67 through_record = through_association.build(*options_for_through_record)
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
68 through_record.send("#{source_reflection.name}=", record)
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
69 through_record
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
70 end
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
71 end
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
72
7154899 @zzak Open extension point for defining options in build_through_record
zzak authored
73 def options_for_through_record
74 [through_scope_attributes]
75 end
76
ec09280 @iantropov Fix insertion of records for hmt association with scope, fix #3548
iantropov authored
77 def through_scope_attributes
9ac81b9 @rafaelfranca Merge pull request #15772 from nbudin/sti_through_bug
rafaelfranca authored
78 scope.where_values_hash(through_association.reflection.name.to_s).
79 except!(through_association.reflection.foreign_key,
80 through_association.reflection.klass.inheritance_column)
ec09280 @iantropov Fix insertion of records for hmt association with scope, fix #3548
iantropov authored
81 end
82
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
83 def save_through_record(record)
84 build_through_record(record).save!
85 ensure
86 @through_records.delete(record.object_id)
87 end
88
2d7ae1b @guilleiguaran Remove mass_assignment_options from ActiveRecord
guilleiguaran authored
89 def build_record(attributes)
edc176d @jonleighton Make sure nested through associations are read only
jonleighton authored
90 ensure_not_nested
d9c4087 @lifo Unify hm:t#create and create! implementation
lifo authored
91
2d7ae1b @guilleiguaran Remove mass_assignment_options from ActiveRecord
guilleiguaran authored
92 record = super(attributes)
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
93
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
94 inverse = source_reflection.inverse_of
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
95 if inverse
bfd0159 @eileencodes reuse available collection? check instead of macro
eileencodes authored
96 if inverse.collection?
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
97 record.send(inverse.name) << build_through_record(record)
5a0b184 @eileencodes add has_one? method and reuse instead of checking macro
eileencodes authored
98 elsif inverse.has_one?
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
99 record.send("#{inverse.name}=", build_through_record(record))
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
100 end
d9c4087 @lifo Unify hm:t#create and create! implementation
lifo authored
101 end
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
102
103 record
d9c4087 @lifo Unify hm:t#create and create! implementation
lifo authored
104 end
105
95e1cf4 @zdennis Fix has_many :through when the source is a belongs_to association. [#…
zdennis authored
106 def target_reflection_has_associated_record?
3ef8d53 @eileencodes reuse available belongs_to? method
eileencodes authored
107 !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
95e1cf4 @zdennis Fix has_many :through when the source is a belongs_to association. [#…
zdennis authored
108 end
109
52f09ea @jonleighton Correctly update counter caches on deletion for has_many :through [#2…
jonleighton authored
110 def update_through_counter?(method)
111 case method
112 when :destroy
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
113 !inverse_updates_counter_cache?(through_reflection)
52f09ea @jonleighton Correctly update counter caches on deletion for has_many :through [#2…
jonleighton authored
114 when :nullify
115 false
116 else
117 true
118 end
9bc75fd @lifo Remove duplicate code from associations. [Pratik]
lifo authored
119 end
8c3b832 @miloops Use ARel in SQL generation through associations
miloops authored
120
05a90c3 @eileencodes rename delete_all_records to delete_or_nullify_all_records
eileencodes authored
121 def delete_or_nullify_all_records(method)
122 delete_records(load_target, method)
d8ae276 @eileencodes begin refactoring delete_records method
eileencodes authored
123 end
124
e62b576 @jonleighton Refactor the implementations of AssociatioCollection#delete and #dest…
jonleighton authored
125 def delete_records(records, method)
edc176d @jonleighton Make sure nested through associations are read only
jonleighton authored
126 ensure_not_nested
9a1a32a @jonleighton Fix naughty trailing whitespace
jonleighton authored
127
b33e7ba @jonleighton s/scoped/scope/
jonleighton authored
128 scope = through_association.scope
fa987cb @guilleiguaran Reverting 16f6f25 (Change behaviour with empty array in where clause)
guilleiguaran authored
129 scope.where! construct_join_attributes(*records)
ad28e00 @lifo Remove unnecessary scoping for creating hm:t join record
lifo authored
130
d55406d @jonleighton Make record.association.destroy(*records) on habtm and hm:t only dele…
jonleighton authored
131 case method
132 when :destroy
7e0cac1 @tenderlove fix deleting join models with no pk
tenderlove authored
133 if scope.klass.primary_key
134 count = scope.destroy_all.length
135 else
bdc1d32 @tgxworld Revert "Reduce allocations when running AR callbacks."
tgxworld authored
136 scope.each { |record| record.run_callbacks :destroy }
7e0cac1 @tenderlove fix deleting join models with no pk
tenderlove authored
137
138 arel = scope.arel
139
a975407 @sgrif Update Arel usage for rails/arel#98fc259
sgrif authored
140 stmt = Arel::DeleteManager.new
7e0cac1 @tenderlove fix deleting join models with no pk
tenderlove authored
141 stmt.from scope.klass.arel_table
142 stmt.wheres = arel.constraints
143
b06f64c @sgrif Remove Relation#bind_params
sgrif authored
144 count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes)
7e0cac1 @tenderlove fix deleting join models with no pk
tenderlove authored
145 end
05bcb8c @jonleighton Support the :dependent option on has_many :through associations. For …
jonleighton authored
146 when :nullify
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
147 count = scope.update_all(source_reflection.foreign_key => nil)
d55406d @jonleighton Make record.association.destroy(*records) on habtm and hm:t only dele…
jonleighton authored
148 else
52f09ea @jonleighton Correctly update counter caches on deletion for has_many :through [#2…
jonleighton authored
149 count = scope.delete_all
f6b12c1 @lifo Refactor HasManyThroughAssociation to inherit from HasManyAssociation…
lifo authored
150 end
151
567d454 @jonleighton Memoize through association
jonleighton authored
152 delete_through_records(records)
9a1a32a @jonleighton Fix naughty trailing whitespace
jonleighton authored
153
dbb7ee1 @dm1try Prevent the counter cache from being decremented twice
dm1try authored
154 if source_reflection.options[:counter_cache] && method != :destroy
a765c84 @matthewrobertson Fix for has_many_through counter_cache bug
matthewrobertson authored
155 counter = source_reflection.counter_cache_column
156 klass.decrement_counter counter, records.map(&:id)
157 end
158
bfd0159 @eileencodes reuse available collection? check instead of macro
eileencodes authored
159 if through_reflection.collection? && update_through_counter?(method)
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
160 update_counter(-count, through_reflection)
23bb8d7 @sgrif Correct errors in counter cache updating
sgrif authored
161 else
162 update_counter(-count)
f6b12c1 @lifo Refactor HasManyThroughAssociation to inherit from HasManyAssociation…
lifo authored
163 end
90099e9 made .find() and class method delegation work on :through relations
Tobias Lütke authored
164 end
0da426b @jeremy Add records to has_many :through using <<, push, and concat by creati…
jeremy authored
165
19b2a5f @jonleighton Remove all revelant through records.
jonleighton authored
166 def through_records_for(record)
167 attributes = construct_join_attributes(record)
168 candidates = Array.wrap(through_association.target)
0a4e3f4 @sgrif Through associations should set both parent ids on join models
sgrif authored
169 candidates.find_all do |c|
170 attributes.all? do |key, value|
171 c.public_send(key) == value
172 end
173 end
19b2a5f @jonleighton Remove all revelant through records.
jonleighton authored
174 end
175
567d454 @jonleighton Memoize through association
jonleighton authored
176 def delete_through_records(records)
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
177 records.each do |record|
19b2a5f @jonleighton Remove all revelant through records.
jonleighton authored
178 through_records = through_records_for(record)
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
179
bfd0159 @eileencodes reuse available collection? check instead of macro
eileencodes authored
180 if through_reflection.collection?
567d454 @jonleighton Memoize through association
jonleighton authored
181 through_records.each { |r| through_association.target.delete(r) }
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
182 else
567d454 @jonleighton Memoize through association
jonleighton authored
183 if through_records.include?(through_association.target)
184 through_association.target = nil
185 end
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
186 end
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
187
188 @through_records.delete(record.object_id)
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
189 end
30a652a @technoweenie Make size for has_many :through use counter cache if it exists. Clos…
technoweenie authored
190 end
191
6abda69 @dhh Added preliminary support for join models [DHH] Added preliminary sup…
dhh authored
192 def find_target
95e1cf4 @zdennis Fix has_many :through when the source is a belongs_to association. [#…
zdennis authored
193 return [] unless target_reflection_has_associated_record?
5cbf73c @tenderlove use cache queries for hm:t associations
tenderlove authored
194 get_records
30a652a @technoweenie Make size for has_many :through use counter cache if it exists. Clos…
technoweenie authored
195 end
ccea983 @h-lame Providing support for :inverse_of as an option to associations.
h-lame authored
196
197 # NOTE - not sure that we can actually cope with inverses here
9f5c18c @jonleighton Refactor we_can_set_the_inverse_on_this? to use a less bizarre name a…
jonleighton authored
198 def invertible_for?(record)
ccea983 @h-lame Providing support for :inverse_of as an option to associations.
h-lame authored
199 false
200 end
6abda69 @dhh Added preliminary support for join models [DHH] Added preliminary sup…
dhh authored
201 end
202 end
203 end
Something went wrong with that request. Please try again.