Flowdock
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%'.
  • :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.
  • :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.
  • :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 set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.

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 :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'
Show source
Register or log in to add new notes.
March 12, 2009
12 thanks

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.

October 22, 2008
10 thanks

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.

August 25, 2010 - (>= v2.3.8)
7 thanks

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.

November 5, 2009 - (>= v2.1.0)
7 thanks

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.

August 17, 2008
6 thanks

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

February 10, 2011 - (<= v2.3.8)
3 thanks

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
October 26, 2010
3 thanks

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
August 17, 2008
1 thank

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%>

February 23, 2009 - (<= v2.2.1)
1 thank

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.