has_many
- 1.0.0 (0)
- 1.1.6 (38)
- 1.2.6 (11)
- 2.0.3 (-14)
- 2.1.0 (14)
- 2.2.1 (13)
- 2.3.8 (8)
- 3.0.0 (32)
- 3.0.9 (-9)
- 3.1.0 (19)
- 3.2.1 (4)
- 3.2.8 (0)
- 3.2.13 (0)
- 4.0.2 (-29)
- 4.1.8 (1)
- 4.2.1 (31)
- 4.2.7 (0)
- 4.2.9 (0)
- 5.0.0.1 (13)
- 5.1.7 (16)
- 5.2.3 (1)
- 6.0.0 (3)
- 6.1.3.1 (17)
- 6.1.7.7 (0)
- 7.0.0 (12)
- 7.1.3.2 (17)
- 7.1.3.4 (0)
- What's this?
has_many(association_id, options = {}, &extension)
public
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 has_many :clients would add among others clients.empty?.
- collection(force_reload = false) - Returns an array of all the associated objects. An empty array is returned if none are found.
- collection<<(object, ...) - Adds one or more objects to the collection by setting their foreign keys to the collection’s primary key.
- collection.delete(object, ...) - 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.
- collection=objects - Replaces the collections content by deleting and adding objects as appropriate.
- collection_singular_ids - Returns an array of the associated objects’ ids
- collection_singular_ids=ids - Replace the collection with the objects identified by the primary keys in ids
- collection.clear - Removes every object from the collection. This destroys the associated objects if they are associated with :dependent => :destroy, deletes them directly from the database if :dependent => :delete_all, otherwise sets their foreign keys to NULL.
- collection.empty? - Returns true if there are no associated objects.
- collection.size - Returns the number of associated objects.
- collection.find - Finds an associated object according to the same rules as Base.find.
- collection.build(attributes = {}, ...) - 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!
- collection.create(attributes = {}) - 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 has_many :clients, which will add:
- Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}")
- Firm#clients<<
- Firm#clients.delete
- Firm#clients=
- Firm#client_ids
- Firm#client_ids=
- Firm#clients.clear
- Firm#clients.empty? (similar to firm.clients.size == 0)
- Firm#clients.size (similar to Client.count "firm_id = #{id}")
- Firm#clients.find (similar to Client.find(id, :conditions => "firm_id = #{id}"))
- Firm#clients.build (similar to Client.new("firm_id" => id))
- Firm#clients.create (similar to c = Client.new("firm_id" => id); c.save; c)
The declaration can also include an options hash to specialize the behavior of the association.
Options are:
- :class_name - Specify the class name of the association. Use it only if that name can’t be inferred from the association name. So has_many :products 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.
- :conditions - Specify the conditions that the associated objects must meet in order to be included as a WHERE SQL fragment, such as price > 5 AND name LIKE 'B%'. Record creations from the association are scoped if a hash is used. has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create or @blog.posts.build.
- :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, such as last_name, first_name DESC.
- :foreign_key - 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.
- :dependent - If set to :destroy all the associated objects are destroyed alongside this object by calling their destroy method. If set to :delete_all all associated objects are deleted without calling their destroy method. If set to :nullify all associated objects’ foreign keys are set to NULL without calling their save callbacks. Warning: This option is ignored when also using the :through option.
- :finder_sql - 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.
- :counter_sql - Specify a complete SQL statement to fetch the size of the association. If :finder_sql is specified but not :counter_sql, :counter_sql will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
- :extend - Specify a named module for extending the proxy. See "Association extensions".
- :include - Specify second-order associations that should be eager loaded when the collection is loaded.
- :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- :limit - An integer determining the limit on the number of rows that should be returned.
- :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
- :select - By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.
- :as - Specifies a polymorphic interface (See belongs_to).
- :through - Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to or has_many association on the join model.
- :source - Specifies the source association name used by has_many :through queries. Only use it if the name cannot be inferred from the association. has_many :subscribers, :through => :subscriptions will look for either :subscribers or :subscriber on Subscription, unless a :source is given.
- :source_type - Specifies type of the source association used by has_many :through queries where the source association is a polymorphic belongs_to.
- :uniq - If true, duplicates will be omitted from the collection. Useful in conjunction with :through.
- :readonly - If true, all the associated objects are readonly through the association.
Option examples:
has_many :comments, :order => "posted_on" has_many :comments, :include => :author has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name" has_many :tracks, :order => "position", :dependent => :destroy has_many :comments, :dependent => :nullify has_many :tags, :as => :taggable has_many :reports, :readonly => true has_many :subscribers, :through => :subscriptions, :source => :user has_many :subscribers, :class_name => "Person", :finder_sql => 'SELECT DISTINCT people.* ' + 'FROM people p, post_subscriptions ps ' + 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' + 'ORDER BY p.first_name'
User a block to extend your associations
You can use blocks to extend your associations with extra methods.
code sample
has_many :children, :dependent => :destroy do def at(time) proxy_owner.children.find_with_deleted :all, :conditions => [ "created_at <= :time AND (deleted_at > :time OR deleted_at IS NULL)", { :time => time } ] end end Model.children.each # do stuff Model.children.at( 1.week.ago ).each # do old stuff
you must use ‘proxy_owner’ to link back to your model.
Gotcha when defining :finder_sql or :counter_sql
When setting custom SQL statements in the :finder_sql or :counter_sql queries, if you need to inject attributes from the current object, such as the ID, make sure to disable string interpolation of the statement by using single quotes or %q().
Example:
has_many :relationships, :class_name => 'Relationship', :finder_sql => %q( SELECT DISTINCT relationships.* FROM relationships WHERE contact_id = #{id} )
Surrounding this SQL with double-quotes or %Q() will expand #{id} too early, resulting in a warning about Object#id being deprecated and general brokenness.
Undocumented :inverse_of option
Support for the :inverse_of option was backported to 2.3.6+.
Here’s the description from the original commit: http://github.com/rails/rails/commit/ccea98389abbf150b886c9f964b1def47f00f237
You can now add an :inverse_of option to has_one, has_many and belongs_to associations. This is best described with an example:
class Man < ActiveRecord::Base has_one :face, :inverse_of => :man end class Face < ActiveRecord::Base belongs_to :man, :inverse_of => :face end m = Man.first f = m.face
Without :inverse_of m and f.man would be different instances of the same object (f.man being pulled from the database again). With these new :inverse_of options m and f.man are the same in memory instance.
Currently :inverse_of supports has_one and has_many (but not the :through variants) associations. It also supplies inverse support for belongs_to associations where the inverse is a has_one and it’s not a polymorphic.
Named scope better than conditions
In modern versions of Rails, in most cases a named_scope is a better alternative to using :conditions on your has_many relations. Compare:
class User has_many :published_posts, :conditions => {:published => true} end user.published_posts
with:
class Post named_scope :published, :conditions => {:published => true} end class User has_many :posts end user.posts.published
It’s better because the Post’s logic (“am I published?”) should not be coupled within User class. This makes it easier to refactor: e.g. if you wanted to refactor the boolean :published field into a :status field with more available values, you would not have to modify User class. Having to modify User when you refactor some implementation detail of Post class is clearly a code smell.
This also applies to :order, :group, :having and similar options.
Explanation about :dependent option
It may seem that :dependent option is only used when the object that has the collection is destroyed, but it is also used every time a associated object is deleted, so if you use
object.collection.delete(associated_object)
your object will be deleted, destroyed or nullified, depending on the value of :dependent option.
With has_many :through associations this option is ignored at least in versions up to 2.1.0, so even if you set :dependent option to :destroy, your join objects will be deleted, not firing any callbacks you have set on destroy events.
If you need to act when your join model is deleted you can use a sweeper or an observer and the association callbacks like this:
# product.rb class Product has_many :categorizations has_many :categories, :through => :categorizations, :before_remove => :fire_before_remove_in_categorizations private def fire_before_remove_in_categorizations(category) categorization = self.categorizations.find_by_category_id(category.id) categorization.class.changed categorization.class.notify_observers(:before_remove, categorization) end end # categorization_sweeper.rb # do not forget to load this sweeper during initialization class CategorizationSweeper < ActionController::Caching::Sweeper observe Categorization def before_remove(categorization) # expire_cache, expire_fragment, whatever end end
One thing you should be aware of it is that you are using before_remove, so you have to be careful because your record may be not be removed (another callback raising an exception or the database not deleting the record) so you can not be sure your object will be delete. Expiring caches is safe because even if your record is not destroyed your cache will be regerated correctly.
You can not use after_remove, because at that point the join model do not exists anymore, so you can not fire its callbacks. But you have the model id and the associated model id, so if you do not need expiring caches maybe you can use this approach (expiring caches can be only done in a sweeper or in a controller, but with after_remove you are bound to your model).
Undocumented callbacks
Not sure why this isn’t documented… there are callbacks for before/after_add and before/after_remove. Example
has_many :things, :after_add => :set_things, :after_remove => :remove_things def set_things(thing) ... end def remove_things(thing) ... end
Polymorphic has_many within inherited class gotcha
Given I have following classes
class User < ActiveRecord::Base end class ::User::Agent < ::User has_many :leads, :as => :creator end
I would expect, that running
User::Agent.first.leads
will result in following query
SELECT "leads".* FROM "leads" WHERE ("leads".creator_id = 6 AND "leads".creator_type = 'User::Agent')
however it results in
SELECT "leads".* FROM "leads" WHERE ("leads".creator_id = 6 AND "leads".creator_type = 'User')
Possible solutions:
-
Make User class use STI - polymorphic relations will then retrieve correct class from :type field (however in my situation it was not an option)
-
If You do never instantiate User class itself, mark it as abstract
class User < ActiveRecord::Base self.abstract_class = true end
-
If You do instantiate User class, as last resort You can overwrite base_class for User::Agent
class ::User::Agent < ::User has_many :leads, :as => :creator def self.base_class self end end
-
If none of above is an option and You do not care that You will lose some of relation’s features, You can always
class User::Agent < ::User has_many :leads, :as => :creator, :finder_sql => %q(SELECT "leads".* FROM "leads" WHERE ("leads".creator_id = #{id} AND "leads".creator_type = 'User::Agent')) end
collection update
in the FirmsController
@firm.people.update(params[:people].keys,params.values)
in the View
<% form_for(@firm) do |f| %>
<%= f.error_messages %> <%= f.text_field :name %> <%@firm.people.each do |person|%> <%fields_for "people[]", person do |pf|%> <%= pf.text_field :name %> <%end%> <%= f.submit "Save" %>
<%end%>
You can't have many :through with habtm
Imagine the following
a has_many b b has_and_belongs_to_many c a has_many c :through => b
a.b works fine
b.c works fine
a.c throws an error!
has_many :through where the through association is a habtm is not supported in Rails. The error is:
ActiveRecord::HasManyThroughSourceAssociationMacroError: Invalid source reflection macro :has_and_belongs_to_many for has_many :stories, :through => :subcategories. Use :source to specify the source reflection
Specifying the source reflection still won’t help you though, because this kind of has_many :through isn’t supported at all.