aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations.rb')
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/associations.rb300
1 files changed, 162 insertions, 138 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 7d27b0607a..fb5f1f8a8c 100755..100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -110,7 +110,7 @@ module ActiveRecord
#
# Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association
# adds a method with that name to its model, it will override the inherited method and break things.
- # For instance, #attributes and #connection would be bad choices for association names.
+ # For instance, +attributes+ and +connection+ would be bad choices for association names.
#
# == Auto-generated methods
#
@@ -258,7 +258,7 @@ module ActiveRecord
# order to update their primary keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
# * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
# is cancelled.
- # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>#association.build</tt> method (documented below).
+ # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>association.build</tt> method (documented below).
# * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
# does not save the parent either.
#
@@ -266,8 +266,8 @@ module ActiveRecord
#
# * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object
# (the owner of the collection) is not yet stored in the database.
- # * If saving any of the objects being added to a collection (via <tt>#push</tt> or similar) fails, then <tt>#push</tt> returns +false+.
- # * You can add an object to a collection without automatically saving it by using the <tt>#collection.build</tt> method (documented below).
+ # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) fails, then <tt>push</tt> returns +false+.
+ # * You can add an object to a collection without automatically saving it by using the <tt>collection.build</tt> method (documented below).
# * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically saved when the parent is saved.
#
# === Association callbacks
@@ -504,8 +504,8 @@ module ActiveRecord
#
# Address.find(:all, :include => :addressable) # INVALID
#
- # will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>. The reason is that the parent model's type
- # is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that early query.
+ # will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model's type
+ # is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that early query.
#
# In versions greater than 2.0.2 eager loading in polymorphic associations is supported
# thanks to a change in the overall preloading strategy.
@@ -575,7 +575,7 @@ module ActiveRecord
# end
# end
#
- # When <tt>Firm#clients</tt> is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
+ # When Firm#clients is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
# with a class in another module scope, this can be done by specifying the complete class name. Example:
#
# module MyApplication
@@ -603,28 +603,28 @@ module ActiveRecord
# Adds the following methods for retrieval and query of collections of associated objects:
# +collection+ is replaced with the symbol passed as the first argument, so
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
- # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
+ # * <tt>collection(force_reload = false)</tt> - Returns an array of all the associated objects.
# An empty array is returned if none are found.
- # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
- # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
+ # * <tt>collection<<(object, ...)</tt> - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
+ # * <tt>collection.delete(object, ...)</tt> - Removes one or more objects from the collection by setting their foreign keys to +NULL+.
# This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
- # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
- # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids
- # * <tt>collection_singular_ids=ids</tt> - replace the collection with the objects identified by the primary keys in +ids+
- # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
+ # * <tt>collection=objects</tt> - Replaces the collections content by deleting and adding objects as appropriate.
+ # * <tt>collection_singular_ids</tt> - Returns an array of the associated objects' ids
+ # * <tt>collection_singular_ids=ids</tt> - Replace the collection with the objects identified by the primary keys in +ids+
+ # * <tt>collection.clear</tt> - Removes every object from the collection. This destroys the associated objects if they
# are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the database if <tt>:dependent => :delete_all</tt>,
- # otherwise sets their foreign keys to NULL.
- # * <tt>collection.empty?</tt> - returns +true+ if there are no associated objects.
- # * <tt>collection.size</tt> - returns the number of associated objects.
- # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
- # * <tt>collection.build(attributes = {}, ...)</tt> - returns one or more new objects of the collection type that have been instantiated
+ # otherwise sets their foreign keys to +NULL+.
+ # * <tt>collection.empty?</tt> - Returns +true+ if there are no associated objects.
+ # * <tt>collection.size</tt> - Returns the number of associated objects.
+ # * <tt>collection.find</tt> - Finds an associated object according to the same rules as Base.find.
+ # * <tt>collection.build(attributes = {}, ...)</tt> - Returns one or more new objects of the collection type that have been instantiated
# with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
# associated object already exists, not if it's +nil+!
- # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
+ # * <tt>collection.create(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated
# with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
# *Note:* This only works if an associated object already exists, not if it's +nil+!
#
- # Example: A +Firm+ class declares <tt>has_many :clients</tt>, which will add:
+ # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
# * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
# * <tt>Firm#clients<<</tt>
# * <tt>Firm#clients.delete</tt>
@@ -640,45 +640,45 @@ module ActiveRecord
# The declaration can also include an options hash to specialize the behavior of the association.
#
# Options are:
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
- # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
- # if the real class name is +SpecialProduct+, you'll have to specify it with this option.
- # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a +WHERE+
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
+ # from the association name. So <tt>has_many :products</tt> will by default be linked to the Product class, but
+ # if the real class name is SpecialProduct, you'll have to specify it with this option.
+ # * <tt>:conditions</tt> - Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from the association are scoped if a hash
# is used. <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
# or <tt>@blog.posts.build</tt>.
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
- # such as <tt>last_name, first_name DESC</tt>
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_many+ association will use +person_id+
- # as the default +foreign_key+.
- # * <tt>:dependent</tt> - if set to <tt>:destroy</tt> all the associated objects are destroyed
- # alongside this object by calling their destroy method. If set to <tt>:delete_all</tt> all associated
- # objects are deleted *without* calling their destroy method. If set to <tt>:nullify</tt> all associated
- # objects' foreign keys are set to +NULL+ *without* calling their save callbacks. *Warning:* This option is ignored when also using
- # the <tt>through</tt> option.
- # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
+ # such as <tt>last_name, first_name DESC</tt>.
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
+ # as the default <tt>:foreign_key</tt>.
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed
+ # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
+ # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
+ # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using
+ # the <tt>:through</tt> option.
+ # * <tt>:finder_sql</tt> - Specify a complete SQL statement to fetch the association. This is a good way to go for complex
# associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
- # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
+ # * <tt>:counter_sql</tt> - Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
# specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
- # * <tt>:extend</tt> - specify a named module for extending the proxy. See "Association extensions".
- # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
- # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
- # * <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.
- # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
+ # * <tt>:extend</tt> - Specify a named module for extending the proxy. See "Association extensions".
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when the collection is loaded.
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
+ # * <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.
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
# but not include the joined columns.
- # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
- # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
+ # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
+ # * <tt>:through</tt> - Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
# or <tt>has_many</tt> association on the join model.
- # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
+ # * <tt>:source</tt> - Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
- # <tt>:subscriber</tt> on +Subscription+, unless a <tt>:source</tt> is given.
- # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
+ # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
+ # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
# association is a polymorphic +belongs_to+.
- # * <tt>:uniq</tt> - if set to +true+, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
- # * <tt>:readonly</tt> - if set to +true+, all the associated objects are readonly through the association.
+ # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
+ # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
#
# Option examples:
# has_many :comments, :order => "posted_on"
@@ -712,14 +712,14 @@ module ActiveRecord
# Adds the following methods for retrieval and query of a single associated object:
# +association+ is replaced with the symbol passed as the first argument, so
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
- # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found.
- # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
+ # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found.
+ # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, sets it as the foreign key,
# and saves the associate object.
- # * <tt>association.nil?</tt> - returns +true+ if there is no associated object.
- # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
+ # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object.
+ # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved. Note: This ONLY works if
# an association already exists. It will NOT work if the association is +nil+.
- # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
+ # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
# with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
#
# Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
@@ -732,28 +732,28 @@ module ActiveRecord
# The declaration can also include an options hash to specialize the behavior of the association.
#
# Options are:
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
- # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
- # if the real class name is +Person+, you'll have to specify it with this option.
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
+ # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
+ # if the real class name is Person, you'll have to specify it with this option.
+ # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>rank = 5</tt>.
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
- # such as <tt>last_name, first_name DESC</tt>
- # * <tt>:dependent</tt> - if set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
+ # such as <tt>last_name, first_name DESC</tt>.
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to <tt>:nullify</tt>, the associated
# object's foreign key is set to +NULL+. Also, association is assigned.
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_one+ association will use +person_id+
- # as the default +foreign_key+.
- # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
- # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
+ # as the default <tt>:foreign_key</tt>.
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
+ # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
# <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
- # * <tt>:source</tt>: Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
+ # * <tt>:source</tt> - Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
- # <tt>:favorite</tt> on +Favorite+, unless a <tt>:source</tt> is given.
- # * <tt>:readonly</tt> - if set to +true+, the associated object is readonly through the association.
+ # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
+ # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -796,12 +796,12 @@ module ActiveRecord
# Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
# +association+ is replaced with the symbol passed as the first argument, so
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
- # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found.
- # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
- # * <tt>association.nil?</tt> - returns +true+ if there is no associated object.
- # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
+ # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found.
+ # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, and sets it as the foreign key.
+ # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object.
+ # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
- # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
+ # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
# with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
#
# Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
@@ -814,34 +814,35 @@ module ActiveRecord
# The declaration can also include an options hash to specialize the behavior of the association.
#
# Options are:
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
- # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
- # if the real class name is +Person+, you'll have to specify it with this option.
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
+ # from the association name. So <tt>has_one :author</tt> will by default be linked to the Author class, but
+ # if the real class name is Person, you'll have to specify it with this option.
+ # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>authorized = 1</tt>.
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
- # such as <tt>last_name, first_name DESC</tt>
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
- # of the association with an +_id+ suffix. So a class that defines a +belongs_to :person+ association will use +person_id+ as the default +foreign_key+.
- # Similarly, +belongs_to :favorite_person, :class_name => "Person"+ will use a foreign key of +favorite_person_id+.
- # * <tt>:dependent</tt> - if set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
+ # such as <tt>last_name, first_name DESC</tt>.
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use
+ # "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt>
+ # will use a foreign key of "favorite_person_id".
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. This option should not be specified when
# <tt>belongs_to</tt> is used in conjunction with a <tt>has_many</tt> relationship on another class because of the potential to leave
# orphaned records behind.
- # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+
+ # * <tt>:counter_cache</tt> - Caches the number of belonging objects on the associate class through the use of +increment_counter+
# and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
- # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging +Comment+ class)
- # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing
+ # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
+ # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
# a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
# When creating a counter cache column, the database statement or migration must specify a default value of <tt>0</tt>, failing to do
- # this results in a counter with NULL value, which will never increment.
- # Note: Specifying a counter_cache will add it to that model's list of readonly attributes using #attr_readonly.
- # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
+ # this results in a counter with +NULL+ value, which will never increment.
+ # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
# Not allowed if the association is polymorphic.
- # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing +true+.
+ # * <tt>:polymorphic</tt> - Specify this association is a polymorphic association by passing +true+.
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
- # to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end).
- # * <tt>:readonly</tt> - if set to +true+, the associated object is readonly through the association.
+ # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
+ # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -926,14 +927,14 @@ module ActiveRecord
end
# Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
- # an option, it is guessed using the lexical order of the class names. So a join between +Developer+ and +Project+
- # will give the default join table name of +developers_projects+ because "D" outranks "P". Note that this precedence
- # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths,
+ # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
+ # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
+ # is calculated using the <tt><</tt> operator for String. This means that if the strings are of different lengths,
# and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
- # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt>
- # to generate a join table name of <tt>papers_paper_boxes</tt> because of the length of the name <tt>paper_boxes</tt>,
- # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the
- # custom <tt>join_table</tt> option if you need to.
+ # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
+ # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
+ # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
+ # custom <tt>:join_table</tt> option if you need to.
#
# Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
# +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as
@@ -943,23 +944,23 @@ module ActiveRecord
# Adds the following methods for retrieval and query:
# +collection+ is replaced with the symbol passed as the first argument, so
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
- # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
+ # * <tt>collection(force_reload = false)</tt> - Returns an array of all the associated objects.
# An empty array is returned if none are found.
- # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
+ # * <tt>collection<<(object, ...)</tt> - Adds one or more objects to the collection by creating associations in the join table
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
- # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
+ # * <tt>collection.delete(object, ...)</tt> - Removes one or more objects from the collection by removing their associations from the join table.
# This does not destroy the objects.
- # * <tt>collection=objects</tt> - replaces the collection's content by deleting and adding objects as appropriate.
- # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids
- # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
- # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
- # * <tt>collection.empty?</tt> - returns +true+ if there are no associated objects.
- # * <tt>collection.size</tt> - returns the number of associated objects.
- # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
+ # * <tt>collection=objects</tt> - Replaces the collection's content by deleting and adding objects as appropriate.
+ # * <tt>collection_singular_ids</tt> - Returns an array of the associated objects' ids.
+ # * <tt>collection_singular_ids=ids</tt> - Replace the collection by the objects identified by the primary keys in +ids+.
+ # * <tt>collection.clear</tt> - Removes every object from the collection. This does not destroy the objects.
+ # * <tt>collection.empty?</tt> - Returns +true+ if there are no associated objects.
+ # * <tt>collection.size</tt> - Returns the number of associated objects.
+ # * <tt>collection.find(id)</tt> - Finds an associated object responding to the +id+ and that
# meets the condition that it has to be associated with this object.
- # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
+ # * <tt>collection.build(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
- # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
+ # * <tt>collection.create(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated
# with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation).
#
# Example: A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
@@ -978,38 +979,38 @@ module ActiveRecord
# The declaration may include an options hash to specialize the behavior of the association.
#
# Options are:
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
# from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
- # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
- # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
+ # Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
+ # * <tt>:join_table</tt> - Specify the name of the join table if the default based on lexical order isn't what you want.
# WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any
# +has_and_belongs_to_many+ declaration in order to work.
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_and_belongs_to_many+ association
- # will use +person_id+ as the default +foreign_key+.
- # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
- # guessed to be the name of the associated class in lower-case and +_id+ suffixed. So if the associated class is +Project+,
- # the +has_and_belongs_to_many+ association will use +project_id+ as the default association +foreign_key+.
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
+ # will use "person_id" as the default <tt>:foreign_key</tt>.
+ # * <tt>:association_foreign_key</tt> - Specify the association foreign key used for the association. By default this is
+ # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project,
+ # the +has_and_belongs_to_many+ association will use "project_id" as the default <tt>:association_foreign_key</tt>.
+ # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
# <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
# or <tt>@blog.posts.build</tt>.
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
# such as <tt>last_name, first_name DESC</tt>
- # * <tt>:uniq</tt> - if set to +true+, duplicate associated objects will be ignored by accessors and query methods
- # * <tt>:finder_sql</tt> - overwrite the default generated SQL statement used to fetch the association with a manual statement
- # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated
- # classes with a manual statement
- # * <tt>:insert_sql</tt> - overwrite the default generated SQL statement used to add links between the associated classes
- # with a manual statement
- # * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
- # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
- # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
- # * <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.
- # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
+ # * <tt>:uniq</tt> - If true, duplicate associated objects will be ignored by accessors and query methods.
+ # * <tt>:finder_sql</tt> - Overwrite the default generated SQL statement used to fetch the association with a manual statement
+ # * <tt>:delete_sql</tt> - Overwrite the default generated SQL statement used to remove links between the associated
+ # classes with a manual statement.
+ # * <tt>:insert_sql</tt> - Overwrite the default generated SQL statement used to add links between the associated classes
+ # with a manual statement.
+ # * <tt>:extend</tt> - Anonymous module for extending the proxy, see "Association extensions".
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when the collection is loaded.
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
+ # * <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.
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
# but not include the joined columns.
- # * <tt>:readonly</tt> - if set to +true+, all the associated objects are readonly through the association.
+ # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
#
# Option examples:
# has_and_belongs_to_many :projects
@@ -1446,6 +1447,12 @@ module ActiveRecord
tables_from_conditions = conditions_tables(options)
tables_from_order = order_tables(options)
all_tables = tables_from_conditions + tables_from_order
+ distinct_join_associations = all_tables.uniq.map{|table|
+ join_dependency.joins_for_table_name(table)
+ }.flatten.compact.uniq
+
+
+
is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
sql = "SELECT "
@@ -1457,7 +1464,7 @@ module ActiveRecord
sql << " FROM #{connection.quote_table_name table_name} "
if is_distinct
- sql << join_dependency.join_associations.reject{ |ja| !all_tables.include?(ja.table_name) }.collect(&:association_join).join
+ sql << distinct_join_associations.collect(&:association_join).join
add_joins!(sql, options, scope)
end
@@ -1617,6 +1624,23 @@ module ActiveRecord
end
end
+ def join_for_table_name(table_name)
+ @joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
+ end
+
+ def joins_for_table_name(table_name)
+ join = join_for_table_name(table_name)
+ result = nil
+ if join && join.is_a?(JoinAssociation)
+ result = [join]
+ if join.parent && join.parent.is_a?(JoinAssociation)
+ result = joins_for_table_name(join.parent.aliased_table_name) +
+ result
+ end
+ end
+ result
+ end
+
protected
def build(associations, parent = nil)
parent ||= @joins.last