method

has_many

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'

8Notes

User a block to extend your associations

THAiSi · Mar 12, 200912 thanks

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

cratchit · Oct 22, 200810 thanks

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.

Named scope better than conditions

szeryf · Nov 5, 20097 thanks

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

yonosoytu · Aug 17, 20086 thanks

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).

Polymorphic has_many within inherited class gotcha

stevo · Oct 26, 20103 thanks

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

Undocumented callbacks

wiseleyb · Feb 9, 20113 thanks

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

collection update

mihserf · Aug 17, 20081 thank

in the FirmsController

@firm.people.update(params[:people].keys,params[:people].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

foliosus · Feb 23, 20091 thank

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.