Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 198 lines (159 sloc) 5.84 kb
54a5446 HasOneThroughAssociation still shouldn't derive from HasManyThroughAs…
Adam Milligan authored
1
6abda69 @dhh Added preliminary support for join models [DHH] Added preliminary sup…
dhh authored
2 module ActiveRecord
fde9504 @rizwanreza Adds title to activerecord/lib/active_record/associations/*
rizwanreza authored
3 # = Active Record Has Many Through Association
6abda69 @dhh Added preliminary support for join models [DHH] Added preliminary sup…
dhh authored
4 module Associations
f6b12c1 @lifo Refactor HasManyThroughAssociation to inherit from HasManyAssociation…
lifo authored
5 class HasManyThroughAssociation < HasManyAssociation #:nodoc:
b049837 @jonleighton Add a HasAssociation module for common code for has_* associations
jonleighton authored
6 include ThroughAssociation
edc176d @jonleighton Make sure nested through associations are read only
jonleighton authored
7
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
8 def initialize(owner, reflection)
9 super
567d454 @jonleighton Memoize through association
jonleighton authored
10
11 @through_records = {}
12 @through_association = nil
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
13 end
14
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -ex…
spastorino authored
15 # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
16 # loaded and calling collection.size if it has. If it's more likely than not that the collection does
17 # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
1ce40ca @neerajdotname ensuring that description does not exceed 100 columns
neerajdotname authored
18 # SELECT query if you use #length.
d51e7f8 @technoweenie Ensure that has_many :through associations use a count query instead …
technoweenie authored
19 def size
ac67eee @jonleighton Use conditionals and implicit returns rather than explicit returns an…
jonleighton authored
20 if has_cached_counter?
1d85a73 @jonleighton Associations - where possible, call attributes methods rather than di…
jonleighton authored
21 owner.send(:read_attribute, cached_counter_attribute_name)
ac67eee @jonleighton Use conditionals and implicit returns rather than explicit returns an…
jonleighton authored
22 elsif loaded?
1d85a73 @jonleighton Associations - where possible, call attributes methods rather than di…
jonleighton authored
23 target.size
ac67eee @jonleighton Use conditionals and implicit returns rather than explicit returns an…
jonleighton authored
24 else
25 count
26 end
d51e7f8 @technoweenie Ensure that has_many :through associations use a count query instead …
technoweenie authored
27 end
8c3b832 @miloops Use ARel in SQL generation through associations
miloops authored
28
1644663 @jonleighton Split AssociationProxy into an Association class (and subclasses) whi…
jonleighton authored
29 def concat(*records)
1d85a73 @jonleighton Associations - where possible, call attributes methods rather than di…
jonleighton authored
30 unless owner.new_record?
7ce7ae0 @jonleighton Get rid of AssociationCollection#save_record
jonleighton authored
31 records.flatten.each do |record|
53b76c1 @wangjohn Adding a bang to method name of raise_on_type_mismatch.
wangjohn authored
32 raise_on_type_mismatch!(record)
7ce7ae0 @jonleighton Get rid of AssociationCollection#save_record
jonleighton authored
33 record.save! if record.new_record?
34 end
35 end
36
37 super
d51e7f8 @technoweenie Ensure that has_many :through associations use a count query instead …
technoweenie authored
38 end
8c3b832 @miloops Use ARel in SQL generation through associations
miloops authored
39
610b632 @ernie Fix collection= on hm:t join models when unsaved
ernie authored
40 def concat_records(records)
41 ensure_not_nested
42
43 records = super
44
45 if owner.new_record? && records
46 records.flatten.each do |record|
47 build_through_record(record)
48 end
49 end
50
51 records
52 end
53
dfaad4f @jonleighton Only save the record once when calling create! on a collection associ…
jonleighton authored
54 def insert_record(record, validate = true, raise = false)
735844d @jonleighton Merge branch 'master' into nested_has_many_through
jonleighton authored
55 ensure_not_nested
dfaad4f @jonleighton Only save the record once when calling create! on a collection associ…
jonleighton authored
56
57 if record.new_record?
58 if raise
59 record.save!(:validate => validate)
60 else
61 return unless record.save(:validate => validate)
62 end
63 end
735844d @jonleighton Merge branch 'master' into nested_has_many_through
jonleighton authored
64
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
65 save_through_record(record)
1644663 @jonleighton Split AssociationProxy into an Association class (and subclasses) whi…
jonleighton authored
66 update_counter(1)
67 record
68 end
f6b12c1 @lifo Refactor HasManyThroughAssociation to inherit from HasManyAssociation…
lifo authored
69
15601c5 @jonleighton Let's be less blasé about method visibility on association proxies
jonleighton authored
70 private
71
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
72 def through_association
567d454 @jonleighton Memoize through association
jonleighton authored
73 @through_association ||= owner.association(through_reflection.name)
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
74 end
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
75
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
76 # We temporarily cache through record that has been build, because if we build a
77 # through record in build_record and then subsequently call insert_record, then we
78 # want to use the exact same object.
79 #
80 # However, after insert_record has been called, we clear the cache entry because
81 # we want it to be possible to have multiple instances of the same record in an
82 # association
83 def build_through_record(record)
84 @through_records[record.object_id] ||= begin
c9c7ee7 @rafaelfranca Not need to pass join attributes to association build
rafaelfranca authored
85 ensure_mutable
86
87 through_record = through_association.build
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
88 through_record.send("#{source_reflection.name}=", record)
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
89 through_record
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
90 end
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
91 end
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
92
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
93 def save_through_record(record)
94 build_through_record(record).save!
95 ensure
96 @through_records.delete(record.object_id)
97 end
98
2d7ae1b @guilleiguaran Remove mass_assignment_options from ActiveRecord
guilleiguaran authored
99 def build_record(attributes)
edc176d @jonleighton Make sure nested through associations are read only
jonleighton authored
100 ensure_not_nested
d9c4087 @lifo Unify hm:t#create and create! implementation
lifo authored
101
2d7ae1b @guilleiguaran Remove mass_assignment_options from ActiveRecord
guilleiguaran authored
102 record = super(attributes)
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
103
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
104 inverse = source_reflection.inverse_of
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
105 if inverse
106 if inverse.macro == :has_many
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
107 record.send(inverse.name) << build_through_record(record)
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
108 elsif inverse.macro == :has_one
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
109 record.send("#{inverse.name}=", build_through_record(record))
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
110 end
d9c4087 @lifo Unify hm:t#create and create! implementation
lifo authored
111 end
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
112
113 record
d9c4087 @lifo Unify hm:t#create and create! implementation
lifo authored
114 end
115
95e1cf4 @zdennis Fix has_many :through when the source is a belongs_to association. [#…
zdennis authored
116 def target_reflection_has_associated_record?
7771595 @acapilleri target_reflection_has_associated_record? refactoring
acapilleri authored
117 !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
95e1cf4 @zdennis Fix has_many :through when the source is a belongs_to association. [#…
zdennis authored
118 end
119
52f09ea @jonleighton Correctly update counter caches on deletion for has_many :through [#2…
jonleighton authored
120 def update_through_counter?(method)
121 case method
122 when :destroy
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
123 !inverse_updates_counter_cache?(through_reflection)
52f09ea @jonleighton Correctly update counter caches on deletion for has_many :through [#2…
jonleighton authored
124 when :nullify
125 false
126 else
127 true
128 end
9bc75fd @lifo Remove duplicate code from associations. [Pratik]
lifo authored
129 end
8c3b832 @miloops Use ARel in SQL generation through associations
miloops authored
130
e62b576 @jonleighton Refactor the implementations of AssociatioCollection#delete and #dest…
jonleighton authored
131 def delete_records(records, method)
edc176d @jonleighton Make sure nested through associations are read only
jonleighton authored
132 ensure_not_nested
9a1a32a @jonleighton Fix naughty trailing whitespace
jonleighton authored
133
80f1694 @jonleighton Perf: Don't load the association for #delete_all.
jonleighton authored
134 # This is unoptimised; it will load all the target records
135 # even when we just want to delete everything.
136 records = load_target if records == :all
137
b33e7ba @jonleighton s/scoped/scope/
jonleighton authored
138 scope = through_association.scope
fa987cb @guilleiguaran Reverting 16f6f25 (Change behaviour with empty array in where clause)
guilleiguaran authored
139 scope.where! construct_join_attributes(*records)
ad28e00 @lifo Remove unnecessary scoping for creating hm:t join record
lifo authored
140
d55406d @jonleighton Make record.association.destroy(*records) on habtm and hm:t only dele…
jonleighton authored
141 case method
142 when :destroy
52f09ea @jonleighton Correctly update counter caches on deletion for has_many :through [#2…
jonleighton authored
143 count = scope.destroy_all.length
05bcb8c @jonleighton Support the :dependent option on has_many :through associations. For …
jonleighton authored
144 when :nullify
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
145 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
146 else
52f09ea @jonleighton Correctly update counter caches on deletion for has_many :through [#2…
jonleighton authored
147 count = scope.delete_all
f6b12c1 @lifo Refactor HasManyThroughAssociation to inherit from HasManyAssociation…
lifo authored
148 end
149
567d454 @jonleighton Memoize through association
jonleighton authored
150 delete_through_records(records)
9a1a32a @jonleighton Fix naughty trailing whitespace
jonleighton authored
151
a765c84 @matthewrobertson Fix for has_many_through counter_cache bug
matthewrobertson authored
152 if source_reflection.options[:counter_cache]
153 counter = source_reflection.counter_cache_column
154 klass.decrement_counter counter, records.map(&:id)
155 end
156
8b00da5 @jonleighton Delegate through_reflection and source_reflection to reflection
jonleighton authored
157 if through_reflection.macro == :has_many && update_through_counter?(method)
158 update_counter(-count, through_reflection)
f6b12c1 @lifo Refactor HasManyThroughAssociation to inherit from HasManyAssociation…
lifo authored
159 end
160
52f09ea @jonleighton Correctly update counter caches on deletion for has_many :through [#2…
jonleighton authored
161 update_counter(-count)
90099e9 made .find() and class method delegation work on :through relations
Tobias Lütke authored
162 end
0da426b @jeremy Add records to has_many :through using <<, push, and concat by creati…
jeremy authored
163
19b2a5f @jonleighton Remove all revelant through records.
jonleighton authored
164 def through_records_for(record)
165 attributes = construct_join_attributes(record)
166 candidates = Array.wrap(through_association.target)
167 candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
168 end
169
567d454 @jonleighton Memoize through association
jonleighton authored
170 def delete_through_records(records)
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
171 records.each do |record|
19b2a5f @jonleighton Remove all revelant through records.
jonleighton authored
172 through_records = through_records_for(record)
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
173
174 if through_reflection.macro == :has_many
567d454 @jonleighton Memoize through association
jonleighton authored
175 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
176 else
567d454 @jonleighton Memoize through association
jonleighton authored
177 if through_records.include?(through_association.target)
178 through_association.target = nil
179 end
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
180 end
71bc921 @jonleighton Fix adding multiple instances of the same record to a has_many :through.
jonleighton authored
181
182 @through_records.delete(record.object_id)
91fd651 @jonleighton Allow building and then later saving has_many :through records, such …
jonleighton authored
183 end
30a652a @technoweenie Make size for has_many :through use counter cache if it exists. Clos…
technoweenie authored
184 end
185
6abda69 @dhh Added preliminary support for join models [DHH] Added preliminary sup…
dhh authored
186 def find_target
95e1cf4 @zdennis Fix has_many :through when the source is a belongs_to association. [#…
zdennis authored
187 return [] unless target_reflection_has_associated_record?
b33e7ba @jonleighton s/scoped/scope/
jonleighton authored
188 scope.to_a
30a652a @technoweenie Make size for has_many :through use counter cache if it exists. Clos…
technoweenie authored
189 end
ccea983 @h-lame Providing support for :inverse_of as an option to associations.
h-lame authored
190
191 # 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
192 def invertible_for?(record)
ccea983 @h-lame Providing support for :inverse_of as an option to associations.
h-lame authored
193 false
194 end
6abda69 @dhh Added preliminary support for join models [DHH] Added preliminary sup…
dhh authored
195 end
196 end
197 end
Something went wrong with that request. Please try again.