rails / rails

Ruby on Rails

This URL has Read+Write access

rails / activerecord / lib / active_record / associations.rb
823554ea » dhh 2005-01-15 Added support for associati... 1 require 'active_record/associations/association_proxy'
db045dbb » dhh 2004-11-23 Initial 2 require 'active_record/associations/association_collection'
823554ea » dhh 2005-01-15 Added support for associati... 3 require 'active_record/associations/belongs_to_association'
57b7532b » dhh 2005-12-01 Work-in progress for provid... 4 require 'active_record/associations/belongs_to_polymorphic_association'
823554ea » dhh 2005-01-15 Added support for associati... 5 require 'active_record/associations/has_one_association'
db045dbb » dhh 2004-11-23 Initial 6 require 'active_record/associations/has_many_association'
6abda696 » dhh 2005-12-02 Added preliminary support f... 7 require 'active_record/associations/has_many_through_association'
db045dbb » dhh 2004-11-23 Initial 8 require 'active_record/associations/has_and_belongs_to_many_association'
9 require 'active_record/deprecated_associations'
10
11 module ActiveRecord
57af961a » technoweenie 2006-03-18 Raise error when trying to ... 12 class HasManyThroughAssociationNotFoundError < ActiveRecordError
13 def initialize(reflection)
14 @reflection = reflection
15 end
16
17 def message
18 "Could not find the association '#{@reflection.options[:through]}' in model #{@reflection.klass}"
19 end
20 end
21
22 class HasManyThroughAssociationPolymorphicError < ActiveRecordError
23 def initialize(owner_class_name, reflection, source_reflection)
24 @owner_class_name = owner_class_name
25 @reflection = reflection
26 @source_reflection = source_reflection
27 end
28
29 def message
30 "Cannot have a has_many :through association '#{@owner_class_name}##{@reflection.name}' on the polymorphic object '#{@source_reflection.class_name}##{@source_reflection.name}'."
31 end
32 end
33
34 class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError
35 def initialize(through_reflection, source_reflection_name)
36 @through_reflection = through_reflection
37 @source_reflection_name = source_reflection_name
38 end
39
40 def message
41 "Could not find the source association '#{@source_reflection_name}' in model #{@through_reflection.klass}"
42 end
43 end
44
45 class EagerLoadPolymorphicError < ActiveRecordError
46 def initialize(reflection)
47 @reflection = reflection
48 end
49
50 def message
51 "Can not eagerly load the polymorphic association '#{@reflection.name}'"
52 end
53 end
54
db045dbb » dhh 2004-11-23 Initial 55 module Associations # :nodoc:
7c8d2f28 » dhh 2005-04-02 Removed broken attempt to D... 56 def self.append_features(base)
57 super
58 base.extend(ClassMethods)
59 end
60
4b229d10 » dhh 2004-12-22 Added Base#clear_associatio... 61 # Clears out the association cache
62 def clear_association_cache #:nodoc:
63 self.class.reflect_on_all_associations.to_a.each do |assoc|
64 instance_variable_set "@#{assoc.name}", nil
deda0ee4 » dhh 2005-06-25 Fixed that clear_associatio... 65 end unless self.new_record?
4b229d10 » dhh 2004-12-22 Added Base#clear_associatio... 66 end
abc895b8 » dhh 2005-04-03 Added new Base.find API and... 67
db045dbb » dhh 2004-11-23 Initial 68 # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
69 # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
a8eea0b0 » dhh 2005-10-26 Fix docs (closes #2491) 70 # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr*
db045dbb » dhh 2004-11-23 Initial 71 # methods. Example:
72 #
73 # class Project < ActiveRecord::Base
74 # belongs_to :portfolio
75 # has_one :project_manager
76 # has_many :milestones
77 # has_and_belongs_to_many :categories
78 # end
79 #
80 # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
823554ea » dhh 2005-01-15 Added support for associati... 81 # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
db045dbb » dhh 2004-11-23 Initial 82 # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
83 # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
84 # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt>
85 # <tt>Project#milestones.build, Project#milestones.create</tt>
86 # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
87 # <tt>Project#categories.delete(category1)</tt>
88 #
89 # == Example
90 #
b921b5da » dhh 2005-04-30 Fixed association picture r... 91 # link:files/examples/associations.png
db045dbb » dhh 2004-11-23 Initial 92 #
93 # == Is it belongs_to or has_one?
94 #
95 # Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
96 # saying belongs_to. Example:
97 #
98 # class Post < ActiveRecord::Base
99 # has_one :author
100 # end
101 #
102 # class Author < ActiveRecord::Base
103 # belongs_to :post
104 # end
105 #
106 # The tables for these classes could look something like:
107 #
108 # CREATE TABLE posts (
109 # id int(11) NOT NULL auto_increment,
110 # title varchar default NULL,
111 # PRIMARY KEY (id)
112 # )
113 #
114 # CREATE TABLE authors (
115 # id int(11) NOT NULL auto_increment,
116 # post_id int(11) default NULL,
117 # name varchar default NULL,
118 # PRIMARY KEY (id)
119 # )
120 #
823554ea » dhh 2005-01-15 Added support for associati... 121 # == Unsaved objects and associations
122 #
123 # You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
124 # aware of, mostly involving the saving of associated objects.
125 #
126 # === One-to-one associations
127 #
a8eea0b0 » dhh 2005-10-26 Fix docs (closes #2491) 128 # * Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in
823554ea » dhh 2005-01-15 Added support for associati... 129 # order to update their primary keys - except if the parent object is unsaved (new_record? == true).
130 # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
131 # is cancelled.
132 # * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
133 # * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
134 # not save the parent either.
135 #
136 # === Collections
137 #
138 # * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
139 # (the owner of the collection) is not yet stored in the database.
140 # * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
141 # * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
142 # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
143 #
17f7f8a0 » dhh 2005-07-06 Made documentation ready fo... 144 # === Association callbacks
4180e57b » dhh 2005-07-04 Added callback hooks to ass... 145 #
146 # Similiar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
147 # trigged when you add an object to or removing an object from a association collection. Example:
148 #
149 # class Project
150 # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
151 #
152 # def evaluate_velocity(developer)
153 # ...
154 # end
155 # end
156 #
157 # It's possible to stack callbacks by passing them as an array. Example:
158 #
159 # class Project
17f7f8a0 » dhh 2005-07-06 Made documentation ready fo... 160 # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
4180e57b » dhh 2005-07-04 Added callback hooks to ass... 161 # end
162 #
163 # Possible callbacks are: before_add, after_add, before_remove and after_remove.
164 #
165 # Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with
166 # the before_remove callbacks, if an exception is thrown the object doesn't get removed.
167 #
8c512a1c » dhh 2005-11-03 Added extension capabilitie... 168 # === Association extensions
169 #
170 # The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
3c01e01d » Marcel Molina 2005-12-19 Fix typo in association doc... 171 # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
8c512a1c » dhh 2005-11-03 Added extension capabilitie... 172 # Example:
173 #
174 # class Account < ActiveRecord::Base
c8dd66fd » dhh 2005-11-06 Made association extensions... 175 # has_many :people do
8c512a1c » dhh 2005-11-03 Added extension capabilitie... 176 # def find_or_create_by_name(name)
e5d9ad3e » dhh 2005-12-12 Added option inheritance fo... 177 # first_name, last_name = name.split(" ", 2)
da7752e5 » dhh 2005-11-06 Sharper example 178 # find_or_create_by_first_name_and_last_name(first_name, last_name)
8c512a1c » dhh 2005-11-03 Added extension capabilitie... 179 # end
c8dd66fd » dhh 2005-11-06 Made association extensions... 180 # end
8c512a1c » dhh 2005-11-03 Added extension capabilitie... 181 # end
182 #
183 # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
184 # person.first_name # => "David"
185 # person.last_name # => "Heinemeier Hansson"
186 #
c8dd66fd » dhh 2005-11-06 Made association extensions... 187 # If you need to share the same extensions between many associations, you can use a named extension module. Example:
188 #
189 # module FindOrCreateByNameExtension
190 # def find_or_create_by_name(name)
e5d9ad3e » dhh 2005-12-12 Added option inheritance fo... 191 # first_name, last_name = name.split(" ", 2)
da7752e5 » dhh 2005-11-06 Sharper example 192 # find_or_create_by_first_name_and_last_name(first_name, last_name)
c8dd66fd » dhh 2005-11-06 Made association extensions... 193 # end
194 # end
195 #
196 # class Account < ActiveRecord::Base
197 # has_many :people, :extend => FindOrCreateByNameExtension
198 # end
199 #
200 # class Company < ActiveRecord::Base
201 # has_many :people, :extend => FindOrCreateByNameExtension
202 # end
8c512a1c » dhh 2005-11-03 Added extension capabilitie... 203 #
db045dbb » dhh 2004-11-23 Initial 204 # == Caching
205 #
206 # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
207 # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
208 # worrying too much about performance at the first go. Example:
209 #
210 # project.milestones # fetches milestones from the database
211 # project.milestones.size # uses the milestone cache
212 # project.milestones.empty? # uses the milestone cache
213 # project.milestones(true).size # fetches milestones from the database
214 # project.milestones # uses the milestone cache
215 #
515886a5 » dhh 2005-04-18 Added documentation for new... 216 # == Eager loading of associations
217 #
218 # Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
219 # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each needs to display their author
220 # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
221 #
222 # class Post < ActiveRecord::Base
223 # belongs_to :author
224 # has_many :comments
225 # end
226 #
227 # Consider the following loop using the class above:
228 #
4e2f7bec » dhh 2005-04-19 Added documentation about :... 229 # for post in Post.find(:all)
515886a5 » dhh 2005-04-18 Added documentation for new... 230 # puts "Post: " + post.title
231 # puts "Written by: " + post.author.name
232 # puts "Last comment on: " + post.comments.first.created_on
233 # end
234 #
235 # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
236 #
4e2f7bec » dhh 2005-04-19 Added documentation about :... 237 # for post in Post.find(:all, :include => :author)
515886a5 » dhh 2005-04-18 Added documentation for new... 238 #
239 # This references the name of the belongs_to association that also used the :author symbol, so the find will now weave in a join something
240 # like this: LEFT OUTER JOIN authors ON authors.id = posts.author_id. Doing so will cut down the number of queries from 201 to 101.
241 #
242 # We can improve upon the situation further by referencing both associations in the finder with:
243 #
4e2f7bec » dhh 2005-04-19 Added documentation about :... 244 # for post in Post.find(:all, :include => [ :author, :comments ])
515886a5 » dhh 2005-04-18 Added documentation for new... 245 #
246 # That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
247 # But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
a8eea0b0 » dhh 2005-10-26 Fix docs (closes #2491) 248 # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
249 # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
515886a5 » dhh 2005-04-18 Added documentation for new... 250 #
851dd080 » dhh 2005-10-18 Added support for using lim... 251 # Please note that limited eager loading with has_many and has_and_belongs_to_many associations is not compatible with describing conditions
252 # on these eager tables. This will work:
253 #
254 # Post.find(:all, :include => :comments, :conditions => "posts.title = 'magic forest'", :limit => 2)
255 #
256 # ...but this will not (and an ArgumentError will be raised):
257 #
258 # Post.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%'", :limit => 2)
4e2f7bec » dhh 2005-04-19 Added documentation about :... 259 #
5462358c » dhh 2005-04-19 Fixed that :get, :post, and... 260 # Also have in mind that since the eager loading is pulling from multiple tables, you'll have to disambiguate any column references
6f344000 » dhh 2005-04-19 Fixed order of loading in e... 261 # in both conditions and orders. So :order => "posts.id DESC" will work while :order => "id DESC" will not. This may require that
262 # you alter the :order and :conditions on the association definitions themselves.
263 #
3022ee84 » dhh 2005-05-21 Added note of limitation fo... 264 # It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will also not pull
265 # additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading.
5462358c » dhh 2005-04-19 Fixed that :get, :post, and... 266 #
db045dbb » dhh 2004-11-23 Initial 267 # == Modules
268 #
269 # By default, associations will look for objects within the current module scope. Consider:
270 #
271 # module MyApplication
272 # module Business
273 # class Firm < ActiveRecord::Base
274 # has_many :clients
275 # end
276 #
277 # class Company < ActiveRecord::Base; end
278 # end
279 # end
280 #
281 # When Firm#clients is called, it'll in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
282 # with a class in another module scope this can be done by specifying the complete class name, such as:
283 #
284 # module MyApplication
285 # module Business
286 # class Firm < ActiveRecord::Base; end
287 # end
288 #
289 # module Billing
290 # class Account < ActiveRecord::Base
291 # belongs_to :firm, :class_name => "MyApplication::Business::Firm"
292 # end
293 # end
294 # end
295 #
296 # == Type safety with ActiveRecord::AssociationTypeMismatch
297 #
298 # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
299 # get a ActiveRecord::AssociationTypeMismatch.
300 #
301 # == Options
302 #
303 # All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones
304 # possible.
305 module ClassMethods
cab24945 » dhh 2005-01-01 Updated documentation for a... 306 # Adds the following methods for retrieval and query of collections of associated objects.
db045dbb » dhh 2004-11-23 Initial 307 # +collection+ is replaced with the symbol passed as the first argument, so
823554ea » dhh 2005-01-15 Added support for associati... 308 # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
db045dbb » dhh 2004-11-23 Initial 309 # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
310 # An empty array is returned if none are found.
311 # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
1ba17797 » dhh 2005-04-02 Added that model.items.dele... 312 # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
313 # This will also destroy the objects if they're declared as belongs_to and dependent on this model.
bfe6a759 » dhh 2005-06-15 Added actual database-chang... 314 # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
315 # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
efaf2af0 » jeremy 2005-09-27 r3653@asus: jeremy | 2005... 316 # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
e18fad6c » dhh 2006-03-18 Update lingering uses of de... 317 # are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
efaf2af0 » jeremy 2005-09-27 r3653@asus: jeremy | 2005... 318 # and sets their foreign keys to NULL otherwise.
db045dbb » dhh 2004-11-23 Initial 319 # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
320 # * <tt>collection.size</tt> - returns the number of associated objects.
c1611a70 » dhh 2005-04-18 Updated documentation here ... 321 # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
db045dbb » dhh 2004-11-23 Initial 322 # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 323 # with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an
a8eea0b0 » dhh 2005-10-26 Fix docs (closes #2491) 324 # associated object already exists, not if it's nil!
db045dbb » dhh 2004-11-23 Initial 325 # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
326 # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
a8eea0b0 » dhh 2005-10-26 Fix docs (closes #2491) 327 # *Note:* This only works if an associated object already exists, not if it's nil!
db045dbb » dhh 2004-11-23 Initial 328 #
329 # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
c1611a70 » dhh 2005-04-18 Updated documentation here ... 330 # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
db045dbb » dhh 2004-11-23 Initial 331 # * <tt>Firm#clients<<</tt>
332 # * <tt>Firm#clients.delete</tt>
bfe6a759 » dhh 2005-06-15 Added actual database-chang... 333 # * <tt>Firm#clients=</tt>
334 # * <tt>Firm#client_ids=</tt>
db045dbb » dhh 2004-11-23 Initial 335 # * <tt>Firm#clients.clear</tt>
336 # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
337 # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
c1611a70 » dhh 2005-04-18 Updated documentation here ... 338 # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
db045dbb » dhh 2004-11-23 Initial 339 # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
1d4d7217 » dhh 2005-06-21 Fixed docs #856 340 # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
db045dbb » dhh 2004-11-23 Initial 341 # The declaration can also include an options hash to specialize the behavior of the association.
342 #
343 # Options are:
098fa943 » dhh 2005-02-07 Fixed documentation snafus ... 344 # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
db045dbb » dhh 2004-11-23 Initial 345 # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
346 # if the real class name is +SpecialProduct+, you'll have to specify it with this option.
347 # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a "WHERE"
348 # sql fragment, such as "price > 5 AND name LIKE 'B%'".
349 # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment,
350 # such as "last_name, first_name DESC"
33092681 » jeremy 2005-11-10 Add :group option, correspo... 351 # * <tt>:group</tt> - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment,
352 # such as "category"
db045dbb » dhh 2004-11-23 Initial 353 # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
354 # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
355 # as the default foreign_key.
57565b35 » NZKoz 2006-03-09 format fix for locking [Mic... 356 # * <tt>:dependent</tt> - if set to :destroy all the associated objects are destroyed
c54b51fa » jeremy 2005-11-08 Deprecate the old, confusin... 357 # alongside this object by calling their destroy method. If set to :delete_all all associated
358 # objects are deleted *without* calling their destroy method. If set to :nullify all associated
359 # objects' foreign keys are set to NULL *without* calling their save callbacks.
57565b35 » NZKoz 2006-03-09 format fix for locking [Mic... 360 # NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy.
db045dbb » dhh 2004-11-23 Initial 361 # May not be set if :exclusively_dependent is also set.
c54b51fa » jeremy 2005-11-08 Deprecate the old, confusin... 362 # * <tt>:exclusively_dependent</tt> - Deprecated; equivalent to :dependent => :delete_all. If set to true all
363 # the associated object are deleted in one SQL statement without having their
db045dbb » dhh 2004-11-23 Initial 364 # before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any
365 # clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
366 # May not be set if :dependent is also set.
367 # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex
a8eea0b0 » dhh 2005-10-26 Fix docs (closes #2491) 368 # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
ea759cb7 » dhh 2004-12-07 Added counter_sql option fo... 369 # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
370 # specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
c8dd66fd » dhh 2005-11-06 Made association extensions... 371 # * <tt>:extend</tt> - specify a named module for extending the proxy, see "Association extensions".
49c801b7 » dhh 2005-11-06 Added :include as an option... 372 # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
e5d9ad3e » dhh 2005-12-12 Added option inheritance fo... 373 # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
374 # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
375 # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
376 # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
377 # include the joined columns.
db045dbb » dhh 2004-11-23 Initial 378 #
379 # Option examples:
380 # has_many :comments, :order => "posted_on"
49c801b7 » dhh 2005-11-06 Added :include as an option... 381 # has_many :comments, :include => :author
db045dbb » dhh 2004-11-23 Initial 382 # has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
57565b35 » NZKoz 2006-03-09 format fix for locking [Mic... 383 # has_many :tracks, :order => "position", :dependent => :destroy
384 # has_many :comments, :dependent => :nullify
db045dbb » dhh 2004-11-23 Initial 385 # has_many :subscribers, :class_name => "Person", :finder_sql =>
386 # 'SELECT DISTINCT people.* ' +
387 # 'FROM people p, post_subscriptions ps ' +
388 # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
389 # 'ORDER BY p.first_name'
c8dd66fd » dhh 2005-11-06 Made association extensions... 390 def has_many(association_id, options = {}, &extension)
6abda696 » dhh 2005-12-02 Added preliminary support f... 391 reflection = create_has_many_reflection(association_id, options, &extension)
04397693 » dhh 2005-09-09 Refactored away all the leg... 392
6abda696 » dhh 2005-12-02 Added preliminary support f... 393 configure_dependency_for_has_many(reflection)
c8dd66fd » dhh 2005-11-06 Made association extensions... 394
6abda696 » dhh 2005-12-02 Added preliminary support f... 395 if options[:through]
396 collection_reader_method(reflection, HasManyThroughAssociation)
397 else
398 add_multiple_associated_save_callbacks(reflection.name)
399 add_association_callbacks(reflection.name, reflection.options)
400 collection_accessor_methods(reflection, HasManyAssociation)
db045dbb » dhh 2004-11-23 Initial 401 end
402
6abda696 » dhh 2005-12-02 Added preliminary support f... 403 add_deprecated_api_for_has_many(reflection.name)
db045dbb » dhh 2004-11-23 Initial 404 end
405
cab24945 » dhh 2005-01-01 Updated documentation for a... 406 # Adds the following methods for retrieval and query of a single associated object.
db045dbb » dhh 2004-11-23 Initial 407 # +association+ is replaced with the symbol passed as the first argument, so
823554ea » dhh 2005-01-15 Added support for associati... 408 # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
db045dbb » dhh 2004-11-23 Initial 409 # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
410 # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
411 # and saves the associate object.
412 # * <tt>association.nil?</tt> - returns true if there is no associated object.
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 413 # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
fd67e3be » dhh 2005-01-25 The create and build method... 414 # with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if
415 # an association already exists. It will NOT work if the association is nil.
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 416 # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
db045dbb » dhh 2004-11-23 Initial 417 # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
418 #
419 # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
3dfa56cc » dhh 2005-06-26 Updated all references to t... 420 # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
db045dbb » dhh 2004-11-23 Initial 421 # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
422 # * <tt>Account#beneficiary.nil?</tt>
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 423 # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
424 # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
425 #
db045dbb » dhh 2004-11-23 Initial 426 # The declaration can also include an options hash to specialize the behavior of the association.
427 #
428 # Options are:
098fa943 » dhh 2005-02-07 Fixed documentation snafus ... 429 # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
db045dbb » dhh 2004-11-23 Initial 430 # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
431 # if the real class name is +Person+, you'll have to specify it with this option.
432 # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
433 # sql fragment, such as "rank = 5".
434 # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
435 # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
a8eea0b0 » dhh 2005-10-26 Fix docs (closes #2491) 436 # * <tt>:dependent</tt> - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also,
7267db58 » dhh 2005-03-06 Added destruction of depend... 437 # association is assigned.
db045dbb » dhh 2004-11-23 Initial 438 # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
439 # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
440 # as the default foreign_key.
49c801b7 » dhh 2005-11-06 Added :include as an option... 441 # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
db045dbb » dhh 2004-11-23 Initial 442 #
443 # Option examples:
57565b35 » NZKoz 2006-03-09 format fix for locking [Mic... 444 # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
445 # has_one :credit_card, :dependent => :nullify # updates the associated records foriegn key value to null rather than destroying it
db045dbb » dhh 2004-11-23 Initial 446 # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
447 # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
448 def has_one(association_id, options = {})
6abda696 » dhh 2005-12-02 Added preliminary support f... 449 reflection = create_has_one_reflection(association_id, options)
db045dbb » dhh 2004-11-23 Initial 450
823554ea » dhh 2005-01-15 Added support for associati... 451 module_eval do
452 after_save <<-EOF
6abda696 » dhh 2005-12-02 Added preliminary support f... 453 association = instance_variable_get("@#{reflection.name}")
3ebde40c » dhh 2005-01-18 Cleanup the proxy rollback ... 454 unless association.nil?
6abda696 » dhh 2005-12-02 Added preliminary support f... 455 association["#{reflection.primary_key_name}"] = id
823554ea » dhh 2005-01-15 Added support for associati... 456 association.save(true)
457 end
458 EOF
459 end
460
6abda696 » dhh 2005-12-02 Added preliminary support f... 461 association_accessor_methods(reflection, HasOneAssociation)
462 association_constructor_method(:build, reflection, HasOneAssociation)
463 association_constructor_method(:create, reflection, HasOneAssociation)
db045dbb » dhh 2004-11-23 Initial 464
6abda696 » dhh 2005-12-02 Added preliminary support f... 465 configure_dependency_for_has_one(reflection)
823554ea » dhh 2005-01-15 Added support for associati... 466
467 # deprecated api
6abda696 » dhh 2005-12-02 Added preliminary support f... 468 deprecated_has_association_method(reflection.name)
469 deprecated_association_comparison_method(reflection.name, reflection.class_name)
db045dbb » dhh 2004-11-23 Initial 470 end
471
cab24945 » dhh 2005-01-01 Updated documentation for a... 472 # Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
db045dbb » dhh 2004-11-23 Initial 473 # +association+ is replaced with the symbol passed as the first argument, so
823554ea » dhh 2005-01-15 Added support for associati... 474 # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
db045dbb » dhh 2004-11-23 Initial 475 # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
476 # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
477 # * <tt>association.nil?</tt> - returns true if there is no associated object.
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 478 # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
823554ea » dhh 2005-01-15 Added support for associati... 479 # with +attributes+ and linked to this object through a foreign key but has not yet been saved.
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 480 # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
823554ea » dhh 2005-01-15 Added support for associati... 481 # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
db045dbb » dhh 2004-11-23 Initial 482 #
e58f2675 » dhh 2005-02-23 Documentation fixes #694 483 # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
db045dbb » dhh 2004-11-23 Initial 484 # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
485 # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
486 # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
487 # * <tt>Post#author.nil?</tt>
1d4d7217 » dhh 2005-06-21 Fixed docs #856 488 # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
489 # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
db045dbb » dhh 2004-11-23 Initial 490 # The declaration can also include an options hash to specialize the behavior of the association.
491 #
492 # Options are:
098fa943 » dhh 2005-02-07 Fixed documentation snafus ... 493 # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
db045dbb » dhh 2004-11-23 Initial 494 # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
495 # if the real class name is +Person+, you'll have to specify it with this option.
496 # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
497 # sql fragment, such as "authorized = 1".
498 # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
499 # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
500 # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
501 # of the associated class in lower-case and "_id" suffixed. So a +Person+ class that makes a belongs_to association to a
502 # +Boss+ class will use "boss_id" as the default foreign_key.
503 # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter
504 # and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
505 # destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
87898bad » jamis 2006-03-09 Allow counter_cache to acce... 506 # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by given that
507 # name instead of a true/false value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
49c801b7 » dhh 2005-11-06 Added :include as an option... 508 # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
db045dbb » dhh 2004-11-23 Initial 509 #
510 # Option examples:
511 # belongs_to :firm, :foreign_key => "client_of"
512 # belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
513 # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
514 # :conditions => 'discounts > #{payments_count}'
823554ea » dhh 2005-01-15 Added support for associati... 515 def belongs_to(association_id, options = {})
6abda696 » dhh 2005-12-02 Added preliminary support f... 516 reflection = create_belongs_to_reflection(association_id, options)
517
518 if reflection.options[:polymorphic]
519 association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
57b7532b » dhh 2005-12-01 Work-in progress for provid... 520
521 module_eval do
522 before_save <<-EOF
6abda696 » dhh 2005-12-02 Added preliminary support f... 523 association = instance_variable_get("@#{reflection.name}")
57b7532b » dhh 2005-12-01 Work-in progress for provid... 524 if !association.nil?
525 if association.new_record?
526 association.save(true)
527 end
528
529 if association.updated?
6abda696 » dhh 2005-12-02 Added preliminary support f... 530 self["#{reflection.primary_key_name}"] = association.id
0859779d » technoweenie 2006-03-15 Allow :dependent options to... 531 self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
57b7532b » dhh 2005-12-01 Work-in progress for provid... 532 end
5213a1f7 » jamis 2005-09-20 Fixed saving a record with ... 533 end
57b7532b » dhh 2005-12-01 Work-in progress for provid... 534 EOF
535 end
536 else
6abda696 » dhh 2005-12-02 Added preliminary support f... 537 association_accessor_methods(reflection, BelongsToAssociation)
538 association_constructor_method(:build, reflection, BelongsToAssociation)
539 association_constructor_method(:create, reflection, BelongsToAssociation)
57b7532b » dhh 2005-12-01 Work-in progress for provid... 540
541 module_eval do
542 before_save <<-EOF
6abda696 » dhh 2005-12-02 Added preliminary support f... 543 association = instance_variable_get("@#{reflection.name}")
57b7532b » dhh 2005-12-01 Work-in progress for provid... 544 if !association.nil?
545 if association.new_record?
546 association.save(true)
547 end
548
549 if association.updated?
6abda696 » dhh 2005-12-02 Added preliminary support f... 550 self["#{reflection.primary_key_name}"] = association.id
57b7532b » dhh 2005-12-01 Work-in progress for provid... 551 end
552 end
553 EOF
554 end
823554ea » dhh 2005-01-15 Added support for associati... 555
57b7532b » dhh 2005-12-01 Work-in progress for provid... 556 # deprecated api
6abda696 » dhh 2005-12-02 Added preliminary support f... 557 deprecated_has_association_method(reflection.name)
558 deprecated_association_comparison_method(reflection.name, reflection.class_name)
57b7532b » dhh 2005-12-01 Work-in progress for provid... 559 end
964b67dd » jamis 2006-03-04 Make counter_cache work wit... 560
561 if options[:counter_cache]
87898bad » jamis 2006-03-09 Allow counter_cache to acce... 562 cache_column = options[:counter_cache] == true ?
563 "#{self.to_s.underscore.pluralize}_count" :
564 options[:counter_cache]
565
964b67dd » jamis 2006-03-04 Make counter_cache work wit... 566 module_eval(
87898bad » jamis 2006-03-09 Allow counter_cache to acce... 567 "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
964b67dd » jamis 2006-03-04 Make counter_cache work wit... 568 " unless #{reflection.name}.nil?'"
569 )
570
571 module_eval(
87898bad » jamis 2006-03-09 Allow counter_cache to acce... 572 "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
964b67dd » jamis 2006-03-04 Make counter_cache work wit... 573 " unless #{reflection.name}.nil?'"
574 )
575 end
823554ea » dhh 2005-01-15 Added support for associati... 576 end
577
db045dbb » dhh 2004-11-23 Initial 578 # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
579 # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
580 # will give the default join table name of "developers_projects" because "D" outranks "P".
581 #
582 # Any additional fields added to the join table will be placed as attributes when pulling records out through
583 # has_and_belongs_to_many associations. This is helpful when have information about the association itself
cab24945 » dhh 2005-01-01 Updated documentation for a... 584 # that you want available on retrieval. Note that any fields in the join table will override matching field names
585 # in the two joined tables. As a consequence, having an "id" field in the join table usually has the undesirable
586 # result of clobbering the "id" fields in either of the other two tables.
db045dbb » dhh 2004-11-23 Initial 587 #
cab24945 » dhh 2005-01-01 Updated documentation for a... 588 # Adds the following methods for retrieval and query.
db045dbb » dhh 2004-11-23 Initial 589 # +collection+ is replaced with the symbol passed as the first argument, so
4eab3758 » dhh 2005-02-23 Finished polishing API docs 590 # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
db045dbb » dhh 2004-11-23 Initial 591 # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
592 # An empty array is returned if none is found.
593 # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
594 # (collection.push and collection.concat are aliases to this method).
595 # * <tt>collection.push_with_attributes(object, join_attributes)</tt> - adds one to the collection by creating an association in the join table that
596 # also holds the attributes from <tt>join_attributes</tt> (should be a hash with the column names as keys). This can be used to have additional
597 # attributes on the join, which will be injected into the associated objects when they are retrieved through the collection.
598 # (collection.concat_with_attributes is an alias to this method).
599 # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
600 # This does not destroy the objects.
bfe6a759 » dhh 2005-06-15 Added actual database-chang... 601 # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
602 # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
db045dbb » dhh 2004-11-23 Initial 603 # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
604 # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
605 # * <tt>collection.size</tt> - returns the number of associated objects.
823554ea » dhh 2005-01-15 Added support for associati... 606 # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
607 # meets the condition that it has to be associated with this object.
db045dbb » dhh 2004-11-23 Initial 608 #
609 # Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
610 # * <tt>Developer#projects</tt>
611 # * <tt>Developer#projects<<</tt>
823554ea » dhh 2005-01-15 Added support for associati... 612 # * <tt>Developer#projects.push_with_attributes</tt>
db045dbb » dhh 2004-11-23 Initial 613 # * <tt>Developer#projects.delete</tt>
bfe6a759 » dhh 2005-06-15 Added actual database-chang... 614 # * <tt>Developer#projects=</tt>
615 # * <tt>Developer#project_ids=</tt>
db045dbb » dhh 2004-11-23 Initial 616 # * <tt>Developer#projects.clear</tt>
617 # * <tt>Developer#projects.empty?</tt>
618 # * <tt>Developer#projects.size</tt>
619 # * <tt>Developer#projects.find(id)</tt>
620 # The declaration may include an options hash to specialize the behavior of the association.
621 #
622 # Options are:
098fa943 » dhh 2005-02-07 Fixed documentation snafus ... 623 # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
db045dbb » dhh 2004-11-23 Initial 624 # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
625 # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
626 # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
627 # WARNING: If you're overwriting the table name of either class, the table_name method MUST be declared underneath any
628 # has_and_belongs_to_many declaration in order to work.
629 # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
630 # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association
631 # will use "person_id" as the default foreign_key.
632 # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
a8eea0b0 » dhh 2005-10-26 Fix docs (closes #2491) 633 # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is +Project+,
634 # the has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
db045dbb » dhh 2004-11-23 Initial 635 # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
636 # sql fragment, such as "authorized = 1".
637 # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC"
638 # * <tt>:uniq</tt> - if set to true, duplicate associated objects will be ignored by accessors and query methods
639 # * <tt>:finder_sql</tt> - overwrite the default generated SQL used to fetch the association with a manual one
640 # * <tt>:delete_sql</tt> - overwrite the default generated SQL used to remove links between the associated
641 # classes with a manual one
642 # * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
643 # with a manual one
8c512a1c » dhh 2005-11-03 Added extension capabilitie... 644 # * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
49c801b7 » dhh 2005-11-06 Added :include as an option... 645 # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
e5d9ad3e » dhh 2005-12-12 Added option inheritance fo... 646 # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
647 # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
648 # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
649 # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
650 # include the joined columns.
db045dbb » dhh 2004-11-23 Initial 651 #
652 # Option examples:
653 # has_and_belongs_to_many :projects
49c801b7 » dhh 2005-11-06 Added :include as an option... 654 # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
db045dbb » dhh 2004-11-23 Initial 655 # has_and_belongs_to_many :nations, :class_name => "Country"
656 # has_and_belongs_to_many :categories, :join_table => "prods_cats"
190e0464 » dhh 2005-05-19 Fixed that :delete_sql in h... 657 # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
658 # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
c8dd66fd » dhh 2005-11-06 Made association extensions... 659 def has_and_belongs_to_many(association_id, options = {}, &extension)
6abda696 » dhh 2005-12-02 Added preliminary support f... 660 reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
661
662 add_multiple_associated_save_callbacks(reflection.name)
663 collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
db045dbb » dhh 2004-11-23 Initial 664
35b4bdcf » jeremy 2005-11-08 Destroy associated has_and_... 665 # Don't use a before_destroy callback since users' before_destroy
666 # callbacks will be executed after the association is wiped out.
6abda696 » dhh 2005-12-02 Added preliminary support f... 667 old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
35b4bdcf » jeremy 2005-11-08 Destroy associated has_and_... 668 class_eval <<-end_eval
669 alias_method :#{old_method}, :destroy_without_callbacks
670 def destroy_without_callbacks
6abda696 » dhh 2005-12-02 Added preliminary support f... 671 #{reflection.name}.clear
35b4bdcf » jeremy 2005-11-08 Destroy associated has_and_... 672 #{old_method}
673 end
674 end_eval
675
6abda696 » dhh 2005-12-02 Added preliminary support f... 676 add_association_callbacks(reflection.name, options)
db045dbb » dhh 2004-11-23 Initial 677
678 # deprecated api
6abda696 » dhh 2005-12-02 Added preliminary support f... 679 deprecated_collection_count_method(reflection.name)
680 deprecated_add_association_relation(reflection.name)
681 deprecated_remove_association_relation(reflection.name)
682 deprecated_has_collection_method(reflection.name)
db045dbb » dhh 2004-11-23 Initial 683 end
684
685 private
686 def join_table_name(first_table_name, second_table_name)
687 if first_table_name < second_table_name
688 join_table = "#{first_table_name}_#{second_table_name}"
689 else
690 join_table = "#{second_table_name}_#{first_table_name}"
691 end
692
693 table_name_prefix + join_table + table_name_suffix
694 end
695
6abda696 » dhh 2005-12-02 Added preliminary support f... 696 def association_accessor_methods(reflection, association_proxy_class)
697 define_method(reflection.name) do |*params|
823554ea » dhh 2005-01-15 Added support for associati... 698 force_reload = params.first unless params.empty?
6abda696 » dhh 2005-12-02 Added preliminary support f... 699 association = instance_variable_get("@#{reflection.name}")
700
701 if association.nil? || force_reload
702 association = association_proxy_class.new(self, reflection)
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 703 retval = association.reload
704 unless retval.nil?
6abda696 » dhh 2005-12-02 Added preliminary support f... 705 instance_variable_set("@#{reflection.name}", association)
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 706 else
6abda696 » dhh 2005-12-02 Added preliminary support f... 707 instance_variable_set("@#{reflection.name}", nil)
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 708 return nil
709 end
710 end
711 association
712 end
713
6abda696 » dhh 2005-12-02 Added preliminary support f... 714 define_method("#{reflection.name}=") do |new_value|
715 association = instance_variable_get("@#{reflection.name}")
3ebde40c » dhh 2005-01-18 Cleanup the proxy rollback ... 716 if association.nil?
6abda696 » dhh 2005-12-02 Added preliminary support f... 717 association = association_proxy_class.new(self, reflection)
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 718 end
6abda696 » dhh 2005-12-02 Added preliminary support f... 719
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 720 association.replace(new_value)
6abda696 » dhh 2005-12-02 Added preliminary support f... 721
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 722 unless new_value.nil?
6abda696 » dhh 2005-12-02 Added preliminary support f... 723 instance_variable_set("@#{reflection.name}", association)
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 724 else
6abda696 » dhh 2005-12-02 Added preliminary support f... 725 instance_variable_set("@#{reflection.name}", nil)
3ebde40c » dhh 2005-01-18 Cleanup the proxy rollback ... 726 return nil
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 727 end
6abda696 » dhh 2005-12-02 Added preliminary support f... 728
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 729 association
730 end
f8783abf » dhh 2005-04-03 Made eager loading work eve... 731
6abda696 » dhh 2005-12-02 Added preliminary support f... 732 define_method("set_#{reflection.name}_target") do |target|
75b8ac80 » dhh 2005-04-14 Dont initialize the associa... 733 return if target.nil?
6abda696 » dhh 2005-12-02 Added preliminary support f... 734 association = association_proxy_class.new(self, reflection)
f8783abf » dhh 2005-04-03 Made eager loading work eve... 735 association.target = target
6abda696 » dhh 2005-12-02 Added preliminary support f... 736 instance_variable_set("@#{reflection.name}", association)
f8783abf » dhh 2005-04-03 Made eager loading work eve... 737 end
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 738 end
739
6abda696 » dhh 2005-12-02 Added preliminary support f... 740 def collection_reader_method(reflection, association_proxy_class)
741 define_method(reflection.name) do |*params|
bce0e149 » dhh 2005-01-18 Fixed that the belongs_to a... 742 force_reload = params.first unless params.empty?
6abda696 » dhh 2005-12-02 Added preliminary support f... 743 association = instance_variable_get("@#{reflection.name}")
744
823554ea » dhh 2005-01-15 Added support for associati... 745 unless association.respond_to?(:loaded?)
6abda696 » dhh 2005-12-02 Added preliminary support f... 746 association = association_proxy_class.new(self, reflection)
747 instance_variable_set("@#{reflection.name}", association)
db045dbb » dhh 2004-11-23 Initial 748 end
6abda696 » dhh 2005-12-02 Added preliminary support f... 749
823554ea » dhh 2005-01-15 Added support for associati... 750 association.reload if force_reload
6abda696 » dhh 2005-12-02 Added preliminary support f... 751
823554ea » dhh 2005-01-15 Added support for associati... 752 association
753 end
6abda696 » dhh 2005-12-02 Added preliminary support f... 754 end
db045dbb » dhh 2004-11-23 Initial 755
6abda696 » dhh 2005-12-02 Added preliminary support f... 756 def collection_accessor_methods(reflection, association_proxy_class)
757 collection_reader_method(reflection, association_proxy_class)
758
759 define_method("#{reflection.name}=") do |new_value|
760 association = instance_variable_get("@#{reflection.name}")
823554ea » dhh 2005-01-15 Added support for associati... 761 unless association.respond_to?(:loaded?)
6abda696 » dhh 2005-12-02 Added preliminary support f... 762 association = association_proxy_class.new(self, reflection)
763 instance_variable_set("@#{reflection.name}", association)
db045dbb » dhh 2004-11-23 Initial 764 end
823554ea » dhh 2005-01-15 Added support for associati... 765 association.replace(new_value)
766 association
767 end
bfe6a759 » dhh 2005-06-15 Added actual database-chang... 768
6abda696 » dhh 2005-12-02 Added preliminary support f... 769 define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
770 send("#{reflection.name}=", reflection.class_name.constantize.find(new_value))
bfe6a759 » dhh 2005-06-15 Added actual database-chang... 771 end
db045dbb » dhh 2004-11-23 Initial 772 end
773
823554ea » dhh 2005-01-15 Added support for associati... 774 def require_association_class(class_name)
775 require_association(Inflector.underscore(class_name)) if class_name
db045dbb » dhh 2004-11-23 Initial 776 end
777
823554ea » dhh 2005-01-15 Added support for associati... 778 def add_multiple_associated_save_callbacks(association_name)
e19bd169 » jeremy 2005-10-01 Association validation does... 779 method_name = "validate_associated_records_for_#{association_name}".to_sym
780 define_method(method_name) do
781 association = instance_variable_get("@#{association_name}")
782 if association.respond_to?(:loaded?)
783 if new_record?
784 association
785 else
786 association.select { |record| record.new_record? }
787 end.each do |record|
788 errors.add "#{association_name}" unless record.valid?
db045dbb » dhh 2004-11-23 Initial 789 end
e19bd169 » jeremy 2005-10-01 Association validation does... 790 end
823554ea » dhh 2005-01-15 Added support for associati... 791 end
db045dbb » dhh 2004-11-23 Initial 792
e19bd169 » jeremy 2005-10-01 Association validation does... 793 validate method_name
c6e01f5b » csshsh 2005-12-21 Fixed that saving a model w... 794 before_save("@new_record_before_save = new_record?; true")
e19bd169 » jeremy 2005-10-01 Association validation does... 795
796 after_callback = <<-end_eval
797 association = instance_variable_get("@#{association_name}")
c66ac210 » csshsh 2005-11-23 Fixed bug where using updat... 798
e19bd169 » jeremy 2005-10-01 Association validation does... 799 if association.respond_to?(:loaded?)
800 if @new_record_before_save
801 records_to_save = association
802 else
803 records_to_save = association.select { |record| record.new_record? }
823554ea » dhh 2005-01-15 Added support for associati... 804 end
e19bd169 » jeremy 2005-10-01 Association validation does... 805 records_to_save.each { |record| association.send(:insert_record, record) }
806 association.send(:construct_sql) # reconstruct the SQL queries now that we know the owner's id
807 end
808 end_eval
c66ac210 » csshsh 2005-11-23 Fixed bug where using updat... 809
e19bd169 » jeremy 2005-10-01 Association validation does... 810 # Doesn't use after_save as that would save associations added in after_create/after_update twice
811 after_create(after_callback)
812 after_update(after_callback)
db045dbb » dhh 2004-11-23 Initial 813 end
abc895b8 » dhh 2005-04-03 Added new Base.find API and... 814
6abda696 » dhh 2005-12-02 Added preliminary support f... 815 def association_constructor_method(constructor, reflection, association_proxy_class)
816 define_method("#{constructor}_#{reflection.name}") do |*params|
2bdaff4a » dhh 2005-06-06 Added a second parameter to... 817 attributees = params.first unless params.empty?
818 replace_existing = params[1].nil? ? true : params[1]
6abda696 » dhh 2005-12-02 Added preliminary support f... 819 association = instance_variable_get("@#{reflection.name}")
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 820
821 if association.nil?
6abda696 » dhh 2005-12-02 Added preliminary support f... 822 association = association_proxy_class.new(self, reflection)
823 instance_variable_set("@#{reflection.name}", association)
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 824 end
825
bfe6a759 » dhh 2005-06-15 Added actual database-chang... 826 if association_proxy_class == HasOneAssociation
827 association.send(constructor, attributees, replace_existing)
828 else
829 association.send(constructor, attributees)
830 end
3b9e90a4 » dhh 2005-04-11 Moved build_association and... 831 end
832 end
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 833
834 def count_with_associations(options = {})
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 835 join_dependency = JoinDependency.new(self, options[:include])
836 return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 837 end
abc895b8 » dhh 2005-04-03 Added new Base.find API and... 838
839 def find_with_associations(options = {})
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 840 join_dependency = JoinDependency.new(self, options[:include])
841 rows = select_all_rows(options, join_dependency)
842 return join_dependency.instantiate(rows)
3a7be80f » dhh 2006-03-15 Change LEFT OUTER JOIN auth... 843 end
6f344000 » dhh 2005-04-19 Fixed order of loading in e... 844
6abda696 » dhh 2005-12-02 Added preliminary support f... 845 def configure_dependency_for_has_many(reflection)
846 if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
847 raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.'
848 end
849
850 if reflection.options[:exclusively_dependent]
851 reflection.options[:dependent] = :delete_all
852 #warn "The :exclusively_dependent option is deprecated. Please use :dependent => :delete_all instead.")
853 end
854
855 # See HasManyAssociation#delete_records. Dependent associations
856 # delete children, otherwise foreign key is set to NULL.
0859779d » technoweenie 2006-03-15 Allow :dependent options to... 857
858 # Add polymorphic type if the :as option is present
859 dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id})
860 if reflection.options[:as]
861 dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'"
862 end
863
6abda696 » dhh 2005-12-02 Added preliminary support f... 864 case reflection.options[:dependent]
865 when :destroy, true
866 module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
867 when :delete_all
0859779d » technoweenie 2006-03-15 Allow :dependent options to... 868 module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
6abda696 » dhh 2005-12-02 Added preliminary support f... 869 when :nullify
0859779d » technoweenie 2006-03-15 Allow :dependent options to... 870 module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }"
6abda696 » dhh 2005-12-02 Added preliminary support f... 871 when nil, false
872 # pass
873 else
57565b35 » NZKoz 2006-03-09 format fix for locking [Mic... 874 raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify'
6abda696 » dhh 2005-12-02 Added preliminary support f... 875 end
876 end
877
878 def configure_dependency_for_has_one(reflection)
879 case reflection.options[:dependent]
880 when :destroy, true
881 module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
882 when :nullify
883 module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
884 when nil, false
885 # pass
886 else
887 raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
888 end
889 end
890
891
892 def add_deprecated_api_for_has_many(association_name)
893 deprecated_collection_count_method(association_name)
894 deprecated_add_association_relation(association_name)
895 deprecated_remove_association_relation(association_name)
896 deprecated_has_collection_method(association_name)
897 deprecated_find_in_collection_method(association_name)
898 deprecated_find_all_in_collection_method(association_name)
899 deprecated_collection_create_method(association_name)
900 deprecated_collection_build_method(association_name)
901 end
902
903 def create_has_many_reflection(association_id, options, &extension)
904 options.assert_valid_keys(
e5d9ad3e » dhh 2005-12-12 Added option inheritance fo... 905 :class_name, :table_name, :foreign_key,
906 :exclusively_dependent, :dependent,
907 :select, :conditions, :include, :order, :group, :limit, :offset,
581f12b7 » Tobias Lütke 2005-12-20 removed :piggyback in favor... 908 :as, :through,
e5d9ad3e » dhh 2005-12-12 Added option inheritance fo... 909 :finder_sql, :counter_sql,
910 :before_add, :after_add, :before_remove, :after_remove,
911 :extend
6abda696 » dhh 2005-12-02 Added preliminary support f... 912 )
913
914 options[:extend] = create_extension_module(association_id, extension) if block_given?
915
8203a2af » dhh 2006-02-26 Dont require association cl... 916 create_reflection(:has_many, association_id, options, self)
6abda696 » dhh 2005-12-02 Added preliminary support f... 917 end
918
919 def create_has_one_reflection(association_id, options)
920 options.assert_valid_keys(
b3065a51 » jeremy 2006-02-09 Polymorphic join support fo... 921 :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
6abda696 » dhh 2005-12-02 Added preliminary support f... 922 )
923
8203a2af » dhh 2006-02-26 Dont require association cl... 924 create_reflection(:has_one, association_id, options, self)
6abda696 » dhh 2005-12-02 Added preliminary support f... 925 end
926
927 def create_belongs_to_reflection(association_id, options)
928 options.assert_valid_keys(
929 :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
930 :counter_cache, :extend, :polymorphic
931 )
932
933 reflection = create_reflection(:belongs_to, association_id, options, self)
934
935 if options[:polymorphic]
936 reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
937 end
938
939 reflection
940 end
941
942 def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
943 options.assert_valid_keys(
e5d9ad3e » dhh 2005-12-12 Added option inheritance fo... 944 :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
945 :select, :conditions, :include, :order, :group, :limit, :offset,
946 :finder_sql, :delete_sql, :insert_sql, :uniq,
947 :before_add, :after_add, :before_remove, :after_remove,
948 :extend
6abda696 » dhh 2005-12-02 Added preliminary support f... 949 )
950
951 options[:extend] = create_extension_module(association_id, extension) if block_given?
952
953 reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
954
955 reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
956
957 reflection
958 end
959
49d0f0cb » dhh 2005-04-18 Speeded up eager loading a ... 960 def reflect_on_included_associations(associations)
251a5d45 » seckar 2005-09-18 Fix eager loading error mes... 961 [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
49d0f0cb » dhh 2005-04-18 Speeded up eager loading a ... 962 end
963
5b9b904f » dhh 2005-07-10 Added support for limit and... 964 def guard_against_unlimitable_reflections(reflections, options)
965 if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
966 raise(
967 ConfigurationError,
968 "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
969 )
970 end
971 end
972
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 973 def select_all_rows(options, join_dependency)
49d0f0cb » dhh 2005-04-18 Speeded up eager loading a ... 974 connection.select_all(
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 975 construct_finder_sql_with_included_associations(options, join_dependency),
49d0f0cb » dhh 2005-04-18 Speeded up eager loading a ... 976 "#{name} Load Including Associations"
977 )
978 end
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 979
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 980 def construct_counter_sql_with_included_associations(options, join_dependency)
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 981 sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"
982
983 # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
984 if !Base.connection.supports_count_distinct?
985 sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}"
986 end
987
988 sql << " FROM #{table_name} "
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 989 sql << join_dependency.join_associations.collect{|join| join.association_join }.join
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 990 sql << "#{options[:joins]} " if options[:joins]
991
992 add_conditions!(sql, options[:conditions])
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 993 add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 994
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 995 add_limit!(sql, options) if using_limitable_reflections?(join_dependency.reflections)
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 996
997 if !Base.connection.supports_count_distinct?
998 sql << ")"
999 end
1000
1001 return sanitize_sql(sql)
1002 end
057cf491 » dhh 2005-04-10 Added support for has_and_b... 1003
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1004 def construct_finder_sql_with_included_associations(options, join_dependency)
1005 sql = "SELECT #{column_aliases(join_dependency)} FROM #{options[:from] || table_name} "
1006 sql << join_dependency.join_associations.collect{|join| join.association_join }.join
efb55d1c » dhh 2005-04-03 Allow order, conditions, an... 1007 sql << "#{options[:joins]} " if options[:joins]
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1008
efb55d1c » dhh 2005-04-03 Allow order, conditions, an... 1009 add_conditions!(sql, options[:conditions])
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1010 add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
851dd080 » dhh 2005-10-18 Added support for using lim... 1011
efb55d1c » dhh 2005-04-03 Allow order, conditions, an... 1012 sql << "ORDER BY #{options[:order]} " if options[:order]
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1013
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1014 add_limit!(sql, options) if using_limitable_reflections?(join_dependency.reflections)
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1015
abc895b8 » dhh 2005-04-03 Added new Base.find API and... 1016 return sanitize_sql(sql)
1017 end
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1018
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1019 def add_limited_ids_condition!(sql, options, join_dependency)
1020 unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
851dd080 » dhh 2005-10-18 Added support for using lim... 1021 sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) "
1022 end
1023 end
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1024
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1025 def select_limited_ids_list(options, join_dependency)
851dd080 » dhh 2005-10-18 Added support for using lim... 1026 connection.select_values(
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1027 construct_finder_sql_for_association_limiting(options, join_dependency),
851dd080 » dhh 2005-10-18 Added support for using lim... 1028 "#{name} Load IDs For Limited Eager Loading"
beff664f » dhh 2005-10-27 Refactor DB exceptions and ... 1029 ).collect { |id| connection.quote(id) }.join(", ")
851dd080 » dhh 2005-10-18 Added support for using lim... 1030 end
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1031
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1032 def construct_finder_sql_for_association_limiting(options, join_dependency)
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1033 #sql = "SELECT DISTINCT #{table_name}.#{primary_key} FROM #{table_name} "
1034 sql = "SELECT "
1035 sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) || include_eager_order?(options)
1036 sql << "#{primary_key} FROM #{table_name} "
1037
1038 if include_eager_conditions?(options) || include_eager_order?(options)
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1039 sql << join_dependency.join_associations.collect{|join| join.association_join }.join
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1040 sql << "#{options[:joins]} " if options[:joins]
1041 end
851dd080 » dhh 2005-10-18 Added support for using lim... 1042
1043 add_conditions!(sql, options[:conditions])
1044 sql << "ORDER BY #{options[:order]} " if options[:order]
1045 add_limit!(sql, options)
1046 return sanitize_sql(sql)
1047 end
1048
1049 def include_eager_conditions?(options)
e466fc45 » dhh 2005-12-07 Fixed that using :include t... 1050 conditions = options[:conditions]
1051 return false unless conditions
1052 conditions = conditions.first if conditions.is_a?(Array)
1053 conditions.scan(/(\w+)\.\w+/).flatten.any? do |condition_table_name|
851dd080 » dhh 2005-10-18 Added support for using lim... 1054 condition_table_name != table_name
1055 end
1056 end
d49a5fcb » NZKoz 2006-02-09 * Fix pagination problems w... 1057
1058 def include_eager_order?(options)
1059 order = options[:order]
1060 return false unless order
1061 order.scan(/(\w+)\.\w+/).flatten.any? do |order_table_name|
1062 order_table_name != table_name
1063 end
1064 end
851dd080 » dhh 2005-10-18 Added support for using lim... 1065
5b9b904f » dhh 2005-07-10 Added support for limit and... 1066 def using_limitable_reflections?(reflections)
1067 reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
1068 end
1069
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1070 def column_aliases(join_dependency)
1071 join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
1072 "#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
057cf491 » dhh 2005-04-10 Added support for has_and_b... 1073 end
1074
4180e57b » dhh 2005-07-04 Added callback hooks to ass... 1075 def add_association_callbacks(association_name, options)
e19bd169 » jeremy 2005-10-01 Association validation does... 1076 callbacks = %w(before_add after_add before_remove after_remove)
1077 callbacks.each do |callback_name|
1078 full_callback_name = "#{callback_name.to_s}_for_#{association_name.to_s}"
1079 defined_callbacks = options[callback_name.to_sym]
1080 if options.has_key?(callback_name.to_sym)
1081 class_inheritable_reader full_callback_name.to_sym
1082 write_inheritable_array(full_callback_name.to_sym, [defined_callbacks].flatten)
1083 end
1084 end
4180e57b » dhh 2005-07-04 Added callback hooks to ass... 1085 end
057cf491 » dhh 2005-04-10 Added support for has_and_b... 1086
851dd080 » dhh 2005-10-18 Added support for using lim... 1087 def condition_word(sql)
1088 sql =~ /where/i ? " AND " : "WHERE "
1089 end
e19bd169 » jeremy 2005-10-01 Association validation does... 1090
c8dd66fd » dhh 2005-11-06 Made association extensions... 1091 def create_extension_module(association_id, extension)
1092 extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
1093
1094 silence_warnings do
1095 Object.const_set(extension_module_name, Module.new(&extension))
1096 end
1097
1098 extension_module_name.constantize
1099 end
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1100
1101 class JoinDependency
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1102 attr_reader :joins, :reflections, :table_aliases
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1103
1104 def initialize(base, associations)
1105 @joins = [JoinBase.new(base)]
1106 @associations = associations
1107 @reflections = []
1108 @base_records_hash = {}
1109 @base_records_in_order = []
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1110 @table_aliases = Hash.new { |aliases, table| aliases[table] = 0 }
1111 @table_aliases[base.table_name] = 1
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1112 build(associations)
1113 end
1114
1115 def join_associations
1116 @joins[1..-1].to_a
1117 end
1118
1119 def join_base
1120 @joins[0]
1121 end
1122
1123 def instantiate(rows)
1124 rows.each_with_index do |row, i|
1125 primary_id = join_base.record_id(row)
1126 unless @base_records_hash[primary_id]
1127 @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
1128 end
1129 construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
1130 end
1131 return @base_records_in_order
1132 end
1133
1134 def aliased_table_names_for(table_name)
1135 joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name}
1136 end
1137
1138 protected
1139 def build(associations, parent = nil)
1140 parent ||= @joins.last
1141 case associations
1142 when Symbol, String
1143 reflection = parent.reflections[associations.to_s.intern] or
1144 raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
1145 @reflections << reflection
1146 @joins << JoinAssociation.new(reflection, self, parent)
1147 when Array
1148 associations.each do |association|
1149 build(association, parent)
1150 end
1151 when Hash
1152 associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1153 build(name, parent)
1154 build(associations[name])
1155 end
1156 else
1157 raise ConfigurationError, associations.inspect
1158 end
1159 end
1160
1161 def construct(parent, associations, joins, row)
1162 case associations
1163 when Symbol, String
1164 while (join = joins.shift).reflection.name.to_s != associations.to_s
1165 raise ConfigurationError, "Not Enough Associations" if joins.empty?
1166 end
1167 construct_association(parent, join, row)
1168 when Array
1169 associations.each do |association|
1170 construct(parent, association, joins, row)
1171 end
1172 when Hash
1173 associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1174 association = construct_association(parent, joins.shift, row)
1175 construct(association, associations[name], joins, row) if association
1176 end
1177 else
1178 raise ConfigurationError, associations.inspect
1179 end
1180 end
1181
1182 def construct_association(record, join, row)
1183 case join.reflection.macro
1184 when :has_many, :has_and_belongs_to_many
1185 collection = record.send(join.reflection.name)
1186 collection.loaded
1187
1188 return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1189 association = join.instantiate(row)
1190 collection.target.push(association) unless collection.target.include?(association)
1191 when :has_one, :belongs_to
1192 return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1193 association = join.instantiate(row)
1194 record.send("set_#{join.reflection.name}_target", association)
1195 else
1196 raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
1197 end
1198 return association
1199 end
1200
1201 class JoinBase
1202 attr_reader :active_record
9c9069a6 » technoweenie 2006-03-18 Fixed has_many :through to ... 1203 delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1204
1205 def initialize(active_record)
1206 @active_record = active_record
1207 @cached_record = {}
1208 end
1209
1210 def aliased_prefix
1211 "t0"
1212 end
1213
1214 def aliased_primary_key
1215 "#{ aliased_prefix }_r0"
1216 end
1217
1218 def aliased_table_name
1219 active_record.table_name
1220 end
1221
1222 def column_names_with_alias
1223 unless @column_names_with_alias
1224 @column_names_with_alias = []
1225 ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
1226 @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
1227 end
1228 end
1229 return @column_names_with_alias
1230 end
1231
1232 def extract_record(row)
1233 column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
1234 end
1235
1236 def record_id(row)
1237 row[aliased_primary_key]
1238 end
1239
1240 def instantiate(row)
1241 @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
1242 end
1243 end
1244
1245 class JoinAssociation < JoinBase
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1246 attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
1247 delegate :options, :klass, :to => :reflection
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1248
1249 def initialize(reflection, join_dependency, parent = nil)
57af961a » technoweenie 2006-03-18 Raise error when trying to ... 1250 reflection.check_validity!
1251 if reflection.options[:polymorphic]
1252 raise EagerLoadPolymorphicError.new(reflection)
1253 end
1254
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1255 super(reflection.klass)
1256 @parent = parent
1257 @reflection = reflection
1258 @aliased_prefix = "t#{ join_dependency.joins.size }"
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1259 @aliased_table_name = sti? ? pluralize(reflection.name) : table_name # start with the table name
1260 @parent_table_name = sti? ? pluralize(parent.active_record.name.underscore) : parent.active_record.table_name
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1261 unless join_dependency.table_aliases[aliased_table_name].zero?
1262 # if the table name has been used, then use an alias
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1263 @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
1264 table_index = join_dependency.table_aliases[aliased_table_name]
1265 @aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1266 end
02d34440 » technoweenie 2006-03-15 Alias the has_and_belongs_t... 1267 if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
1268 @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : parent.active_record.reflect_on_association(reflection.options[:through]).klass.table_name
1269 unless join_dependency.table_aliases[aliased_join_table_name].zero?
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1270 @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
1271 table_index = join_dependency.table_aliases[aliased_join_table_name]
1272 @aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
02d34440 » technoweenie 2006-03-15 Alias the has_and_belongs_t... 1273 end
1274 join_dependency.table_aliases[aliased_join_table_name] += 1
1275 end
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1276 join_dependency.table_aliases[aliased_table_name] += 1
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1277 end
1278
1279 def association_join
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1280 join = case reflection.macro
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1281 when :has_and_belongs_to_many
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1282 " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1283 table_alias_for(options[:join_table], aliased_join_table_name),
1284 aliased_join_table_name,
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1285 options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
1286 reflection.active_record.table_name, reflection.active_record.primary_key] +
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1287 " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1288 table_name_and_alias, aliased_table_name, klass.primary_key,
a3502c41 » technoweenie 2006-03-15 Use association's :conditio... 1289 aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1290 ]
1291 when :has_many, :has_one
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1292 case
1293 when reflection.macro == :has_many && reflection.options[:through]
a3502c41 » technoweenie 2006-03-15 Use association's :conditio... 1294 through_reflection = parent.active_record.reflect_on_association(reflection.options[:through])
9c9069a6 » technoweenie 2006-03-18 Fixed has_many :through to ... 1295 through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1296 if through_reflection.options[:as] # has_many :through against a polymorphic join
1297 polymorphic_foreign_key = through_reflection.options[:as].to_s + '_id'
1298 polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'
1299
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1300 " LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) " % [
1301 table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
02d34440 » technoweenie 2006-03-15 Alias the has_and_belongs_t... 1302 aliased_join_table_name, polymorphic_foreign_key,
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1303 parent.aliased_table_name, parent.primary_key,
02d34440 » technoweenie 2006-03-15 Alias the has_and_belongs_t... 1304 aliased_join_table_name, polymorphic_foreign_type, klass.quote(parent.active_record.base_class.name)] +
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1305 " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
02d34440 » technoweenie 2006-03-15 Alias the has_and_belongs_t... 1306 aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1307 ]
1308 else # has_many :through against a normal join
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1309 " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1310 table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
02d34440 » technoweenie 2006-03-15 Alias the has_and_belongs_t... 1311 through_reflection.options[:foreign_key] || through_reflection.active_record.to_s.classify.foreign_key,
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1312 parent.aliased_table_name, parent.primary_key] +
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1313 " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1314 table_name_and_alias,
02d34440 » technoweenie 2006-03-15 Alias the has_and_belongs_t... 1315 aliased_table_name, primary_key,
1316 aliased_join_table_name, options[:foreign_key] || klass.to_s.classify.foreign_key
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1317 ]
1318 end
1319
1320 when reflection.macro == :has_many && reflection.options[:as]
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1321 " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
1322 table_name_and_alias,
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1323 aliased_table_name, "#{reflection.options[:as]}_id",
1324 parent.aliased_table_name, parent.primary_key,
1325 aliased_table_name, "#{reflection.options[:as]}_type",
7c4b7aff » technoweenie 2006-03-06 Fix quoting of inheritance ... 1326 klass.quote(parent.active_record.base_class.name)
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1327 ]
1328 else
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1329 " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1330 table_name_and_alias,
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1331 aliased_table_name, options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
1332 parent.aliased_table_name, parent.primary_key
1333 ]
1334 end
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1335 when :belongs_to
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1336 " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1337 table_name_and_alias, aliased_table_name, reflection.klass.primary_key,
6c67905c » dhh 2006-03-18 Fixed that eager loading fr... 1338 parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1339 ]
1340 else
1341 ""
a3502c41 » technoweenie 2006-03-15 Use association's :conditio... 1342 end || ''
7c4b7aff » technoweenie 2006-03-06 Fix quoting of inheritance ... 1343 join << %(AND %s.%s = %s ) % [
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1344 aliased_table_name,
7c4b7aff » technoweenie 2006-03-06 Fix quoting of inheritance ... 1345 reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column),
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1346 klass.quote(klass.name)] if sti?
9c9069a6 » technoweenie 2006-03-18 Fixed has_many :through to ... 1347 join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
4f00c705 » dhh 2006-03-05 Fixed eager loading problem... 1348 join
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1349 end
229c0f43 » technoweenie 2006-03-17 Rework table aliasing to ac... 1350
1351 protected
1352 def sti?
1353 !klass.descends_from_active_record?
1354 end
1355
1356 def pluralize(table_name)
1357 ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
1358 end
1359
1360 def table_alias_for(table_name, table_alias)
1361 "#{table_name} #{table_alias if table_name != table_alias}".strip
1362 end
1363
1364 def table_name_and_alias
1365 table_alias_for table_name, @aliased_table_name
1366 end
9c9069a6 » technoweenie 2006-03-18 Fixed has_many :through to ... 1367
1368 def interpolate_sql(sql)
1369 instance_eval("%@#{sql.gsub('@', '\@')}@")
1370 end
55854c41 » dhh 2006-03-04 Added cascading eager loadi... 1371 end
1372 end
c8dd66fd » dhh 2005-11-06 Made association extensions... 1373 end
db045dbb » dhh 2004-11-23 Initial 1374 end
823554ea » dhh 2005-01-15 Added support for associati... 1375 end