· 8 years ago · Apr 17, 2018, 09:20 PM
1require 'active_record/associations/association_proxy'
2require 'active_record/associations/association_collection'
3require 'active_record/associations/belongs_to_association'
4require 'active_record/associations/belongs_to_polymorphic_association'
5require 'active_record/associations/has_one_association'
6require 'active_record/associations/has_many_association'
7require 'active_record/associations/has_many_through_association'
8require 'active_record/associations/has_and_belongs_to_many_association'
9
10module ActiveRecord
11class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
12def initialize(owner_class_name, reflection)
13super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
14end
15end
16
17class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
18def initialize(owner_class_name, reflection, source_reflection)
19super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
20end
21end
22
23class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
24def initialize(owner_class_name, reflection, source_reflection)
25super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
26end
27end
28
29class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
30def initialize(reflection)
31through_reflection = reflection.through_reflection
32source_reflection_names = reflection.source_reflection_names
33source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
34super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
35end
36end
37
38class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
39def initialize(reflection)
40through_reflection = reflection.through_reflection
41source_reflection = reflection.source_reflection
42super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
43end
44end
45
46class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
47def initialize(owner, reflection)
48super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
49end
50end
51
52class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc:
53def initialize(owner, reflection)
54super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
55end
56end
57
58class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
59def initialize(reflection)
60super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
61end
62end
63
64class ReadOnlyAssociation < ActiveRecordError #:nodoc:
65def initialize(reflection)
66super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
67end
68end
69
70module Associations # :nodoc:
71def self.included(base)
72base.extend(ClassMethods)
73end
74
75# Clears out the association cache
76def clear_association_cache #:nodoc:
77self.class.reflect_on_all_associations.to_a.each do |assoc|
78instance_variable_set "@#{assoc.name}", nil
79end unless self.new_record?
80end
81
82# Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
83# "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
84# specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
85# methods. Example:
86#
87# class Project < ActiveRecord::Base
88# belongs_to :portfolio
89# has_one :project_manager
90# has_many :milestones
91# has_and_belongs_to_many :categories
92# end
93#
94# The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
95# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
96# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
97# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
98# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt>
99# <tt>Project#milestones.build, Project#milestones.create</tt>
100# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
101# <tt>Project#categories.delete(category1)</tt>
102#
103# === A word of warning
104#
105# Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association
106# adds a method with that name to its model, it will override the inherited method and break things.
107# For instance, #attributes and #connection would be bad choices for association names.
108#
109# == Auto-generated methods
110#
111# ===Singular associations (one-to-one)
112# | | belongs_to |
113# generated methods | belongs_to | :polymorphic | has_one
114# ----------------------------------+------------+--------------+---------
115# #other | X | X | X
116# #other=(other) | X | X | X
117# #build_other(attributes={}) | X | | X
118# #create_other(attributes={}) | X | | X
119# #other.create!(attributes={}) | | | X
120# #other.nil? | X | X |
121#
122# ===Collection associations (one-to-many / many-to-many)
123# | | | has_many
124# generated methods | habtm | has_many | :through
125# ----------------------------------+-------+----------+----------
126# #others | X | X | X
127# #others=(other,other,...) | X | X |
128# #other_ids | X | X | X
129# #other_ids=(id,id,...) | X | X |
130# #others<< | X | X | X
131# #others.push | X | X | X
132# #others.concat | X | X | X
133# #others.build(attributes={}) | X | X |
134# #others.create(attributes={}) | X | X |
135# #others.create!(attributes={}) | X | X | X
136# #others.size | X | X | X
137# #others.length | X | X | X
138# #others.count | | X | X
139# #others.sum(args*,&block) | X | X | X
140# #others.empty? | X | X | X
141# #others.clear | X | X |
142# #others.delete(other,other,...) | X | X | X
143# #others.delete_all | X | X |
144# #others.destroy_all | X | X | X
145# #others.find(*args) | X | X | X
146# #others.find_first | X | |
147# #others.uniq | X | X |
148# #others.reset | X | X | X
149#
150# == Cardinality and associations
151#
152# ActiveRecord associations can be used to describe relations with one-to-one, one-to-many
153# and many-to-many cardinality. Each model uses an association to describe its role in
154# the relation. In each case, the +belongs_to+ association is used in the model that has
155# the foreign key.
156#
157# === One-to-one
158#
159# Use +has_one+ in the base, and +belongs_to+ in the associated model.
160#
161# class Employee < ActiveRecord::Base
162# has_one :office
163# end
164# class Office < ActiveRecord::Base
165# belongs_to :employee # foreign key - employee_id
166# end
167#
168# === One-to-many
169#
170# Use +has_many+ in the base, and +belongs_to+ in the associated model.
171#
172# class Manager < ActiveRecord::Base
173# has_many :employees
174# end
175# class Employee < ActiveRecord::Base
176# belongs_to :manager # foreign key - manager_id
177# end
178#
179# === Many-to-many
180#
181# There are two ways to build a many-to-many relationship.
182#
183# The first way uses a +has_many+ association with the <tt>:through</tt> option and a join model, so
184# there are two stages of associations.
185#
186# class Assignment < ActiveRecord::Base
187# belongs_to :programmer # foreign key - programmer_id
188# belongs_to :project # foreign key - project_id
189# end
190# class Programmer < ActiveRecord::Base
191# has_many :assignments
192# has_many :projects, :through => :assignments
193# end
194# class Project < ActiveRecord::Base
195# has_many :assignments
196# has_many :programmers, :through => :assignments
197# end
198#
199# For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
200# that has no corresponding model or primary key.
201#
202# class Programmer < ActiveRecord::Base
203# has_and_belongs_to_many :projects # foreign keys in the join table
204# end
205# class Project < ActiveRecord::Base
206# has_and_belongs_to_many :programmers # foreign keys in the join table
207# end
208#
209# Choosing which way to build a many-to-many relationship is not always simple.
210# If you need to work with the relationship model as its own entity,
211# use <tt>has_many :through</tt>. Use +has_and_belongs_to_many+ when working with legacy schemas or when
212# you never work directly with the relationship itself.
213#
214# == Is it a +belongs_to+ or +has_one+ association?
215#
216# Both express a 1-1 relationship. The difference is mostly where to place the foreign key, which goes on the table for the class
217# declaring the +belongs_to+ relationship. Example:
218#
219# class User < ActiveRecord::Base
220# # I reference an account.
221# belongs_to :account
222# end
223#
224# class Account < ActiveRecord::Base
225# # One user references me.
226# has_one :user
227# end
228#
229# The tables for these classes could look something like:
230#
231# CREATE TABLE users (
232# id int(11) NOT NULL auto_increment,
233# account_id int(11) default NULL,
234# name varchar default NULL,
235# PRIMARY KEY (id)
236# )
237#
238# CREATE TABLE accounts (
239# id int(11) NOT NULL auto_increment,
240# name varchar default NULL,
241# PRIMARY KEY (id)
242# )
243#
244# == Unsaved objects and associations
245#
246# You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
247# aware of, mostly involving the saving of associated objects.
248#
249# === One-to-one associations
250#
251# * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
252# order to update their primary keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
253# * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
254# is cancelled.
255# * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>#association.build</tt> method (documented below).
256# * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
257# does not save the parent either.
258#
259# === Collections
260#
261# * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object
262# (the owner of the collection) is not yet stored in the database.
263# * If saving any of the objects being added to a collection (via <tt>#push</tt> or similar) fails, then <tt>#push</tt> returns +false+.
264# * You can add an object to a collection without automatically saving it by using the <tt>#collection.build</tt> method (documented below).
265# * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically saved when the parent is saved.
266#
267# === Association callbacks
268#
269# Similar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
270# triggered when you add an object to or remove an object from an association collection. Example:
271#
272# class Project
273# has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
274#
275# def evaluate_velocity(developer)
276# ...
277# end
278# end
279#
280# It's possible to stack callbacks by passing them as an array. Example:
281#
282# class Project
283# has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
284# end
285#
286# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
287#
288# Should any of the +before_add+ callbacks throw an exception, the object does not get added to the collection. Same with
289# the +before_remove+ callbacks; if an exception is thrown the object doesn't get removed.
290#
291# === Association extensions
292#
293# The proxy objects that control the access to associations can be extended through anonymous modules. This is especially
294# beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
295# Example:
296#
297# class Account < ActiveRecord::Base
298# has_many :people do
299# def find_or_create_by_name(name)
300# first_name, last_name = name.split(" ", 2)
301# find_or_create_by_first_name_and_last_name(first_name, last_name)
302# end
303# end
304# end
305#
306# person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
307# person.first_name # => "David"
308# person.last_name # => "Heinemeier Hansson"
309#
310# If you need to share the same extensions between many associations, you can use a named extension module. Example:
311#
312# module FindOrCreateByNameExtension
313# def find_or_create_by_name(name)
314# first_name, last_name = name.split(" ", 2)
315# find_or_create_by_first_name_and_last_name(first_name, last_name)
316# end
317# end
318#
319# class Account < ActiveRecord::Base
320# has_many :people, :extend => FindOrCreateByNameExtension
321# end
322#
323# class Company < ActiveRecord::Base
324# has_many :people, :extend => FindOrCreateByNameExtension
325# end
326#
327# If you need to use multiple named extension modules, you can specify an array of modules with the <tt>:extend</tt> option.
328# In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
329# those earlier in the array. Example:
330#
331# class Account < ActiveRecord::Base
332# has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
333# end
334#
335# Some extensions can only be made to work with knowledge of the association proxy's internals.
336# Extensions can access relevant state using accessors on the association proxy:
337#
338# * +proxy_owner+ - Returns the object the association is part of.
339# * +proxy_reflection+ - Returns the reflection object that describes the association.
340# * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
341#
342# === Association Join Models
343#
344# Has Many associations can be configured with the <tt>:through</tt> option to use an explicit join model to retrieve the data. This
345# operates similarly to a +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
346# callbacks, and extra attributes on the join model. Consider the following schema:
347#
348# class Author < ActiveRecord::Base
349# has_many :authorships
350# has_many :books, :through => :authorships
351# end
352#
353# class Authorship < ActiveRecord::Base
354# belongs_to :author
355# belongs_to :book
356# end
357#
358# @author = Author.find :first
359# @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
360# @author.books # selects all books by using the Authorship join model
361#
362# You can also go through a +has_many+ association on the join model:
363#
364# class Firm < ActiveRecord::Base
365# has_many :clients
366# has_many :invoices, :through => :clients
367# end
368#
369# class Client < ActiveRecord::Base
370# belongs_to :firm
371# has_many :invoices
372# end
373#
374# class Invoice < ActiveRecord::Base
375# belongs_to :client
376# end
377#
378# @firm = Firm.find :first
379# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
380# @firm.invoices # selects all invoices by going through the Client join model.
381#
382# === Polymorphic Associations
383#
384# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
385# specify an interface that a +has_many+ association must adhere to.
386#
387# class Asset < ActiveRecord::Base
388# belongs_to :attachable, :polymorphic => true
389# end
390#
391# class Post < ActiveRecord::Base
392# has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
393# end
394#
395# @asset.attachable = @post
396#
397# This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
398# an +attachable_id+ integer column and an +attachable_type+ string column.
399#
400# Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
401# for the associations to work as expected, ensure that you store the base model for the STI models in the
402# type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
403# and member posts that use the posts table for STI. In this case, there must be a +type+ column in the posts table.
404#
405# class Asset < ActiveRecord::Base
406# belongs_to :attachable, :polymorphic => true
407#
408# def attachable_type=(sType)
409# super(sType.to_s.classify.constantize.base_class.to_s)
410# end
411# end
412#
413# class Post < ActiveRecord::Base
414# # because we store "Post" in attachable_type now :dependent => :destroy will work
415# has_many :assets, :as => :attachable, :dependent => :destroy
416# end
417#
418# class GuestPost < Post
419# end
420#
421# class MemberPost < Post
422# end
423#
424# == Caching
425#
426# All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
427# instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
428# worrying too much about performance at the first go. Example:
429#
430# project.milestones # fetches milestones from the database
431# project.milestones.size # uses the milestone cache
432# project.milestones.empty? # uses the milestone cache
433# project.milestones(true).size # fetches milestones from the database
434# project.milestones # uses the milestone cache
435#
436# == Eager loading of associations
437#
438# 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
439# one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author
440# triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
441#
442# class Post < ActiveRecord::Base
443# belongs_to :author
444# has_many :comments
445# end
446#
447# Consider the following loop using the class above:
448#
449# for post in Post.find(:all)
450# puts "Post: " + post.title
451# puts "Written by: " + post.author.name
452# puts "Last comment on: " + post.comments.first.created_on
453# end
454#
455# To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
456#
457# for post in Post.find(:all, :include => :author)
458#
459# This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol, so the find will now weave in a join something
460# like this: <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Doing so will cut down the number of queries from 201 to 101.
461#
462# We can improve upon the situation further by referencing both associations in the finder with:
463#
464# for post in Post.find(:all, :include => [ :author, :comments ])
465#
466# That'll add another join along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt>. And we'll be down to 1 query.
467#
468# To include a deep hierarchy of associations, use a hash:
469#
470# for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
471#
472# That'll grab not only all the comments but all their authors and gravatar pictures. You can mix and match
473# symbols, arrays and hashes in any combination to describe the associations you want to load.
474#
475# All of this power shouldn't fool you into thinking that you can pull out huge amounts of data with no performance penalty just because you've reduced
476# 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
477# 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.
478#
479# Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
480# <tt>:order => "posts.id DESC"</tt> will work while <tt>:order => "id DESC"</tt> will not. Because eager loading generates the +SELECT+ statement too, the
481# <tt>:select</tt> option is ignored.
482#
483# You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
484# as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
485# associations" with +has_and_belongs_to_many+ are not a good fit for eager loading.
486#
487# When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
488# before the actual model exists.
489#
490# Eager loading is not supported with polymorphic associations up to (and including)
491# version 2.0.2. Given
492#
493# class Address < ActiveRecord::Base
494# belongs_to :addressable, :polymorphic => true
495# end
496#
497# a call that tries to eager load the addressable model
498#
499# Address.find(:all, :include => :addressable) # INVALID
500#
501# will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>. The reason is that the parent model's type
502# is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that early query.
503#
504# In versions greater than 2.0.2 eager loading in polymorphic associations is supported
505# thanks to a change in the overall preloading strategy.
506#
507# It does work t