Space Vatican

Ramblings of a curious coder

Creating Multiple Associations With the Same Table

Rails makes setting up your associations dead easy and very concise. There’s a whole bunch of options that can be set however as long as you are following Rails’ conventions you very rarely need to use them. If you’re new to rails you might not even know they exists and get away with it 99% of the time. One case where you do is when you’ve got more than one association pointing at the same table.

If you wrote an application tracking sales between individuals (lets call it railsbay) you might have a table called sales and in that table you’d need to track the buyer and the seller. Both buyer and seller should be objects from the users table, and we’ve got two columns that refer to them: buyer_id and seller_id. Before we tackle what we actually want to do, lets take a step back. This would all be really easy if there was only one user associated with a sale. So lets take a look at what happens inside when we write

1
2
3
class Sale < ActiveRecord::Base
  belongs_to :user
end

When you do this [1], ActiveRecord derives the class name by taking :user and camelizing it to get User, the assumed class name (if this was a has_many, it would also singularize, so Users -> User). If you specify a class_name option, that takes priority. The foreign key (the database column containing the id of the associated user, in this case user_id) is derived by taking the name (user) and adding _id [1]. It could be overridden with :foreign_key. The first parameter (:user) is the name of association, so in particular it is the name of the method you call to get the value of the association. All this means is that we could also have written

1
2
3
class Sale < ActiveRecord::Base
  belongs_to :user, :class_name => 'User', :foreign_key => 'user_id'
end

It’s rather verbose, so we don’t. If you didn’t like your users (that’s not really a good idea) you can say

1
2
3
class Sale < ActiveRecord::Base
  belongs_to :nasty_user, :class_name => 'User', :foreign_key => 'user_id'
end

and now you need to access the association as sale.nasty_user instead of sale.user.

We can now return to the original problem. We’ve got 2 users to associate, a buyer and a seller, so the associations might as well use those names. Our foreign keys are buyer_id and seller_id, and in both cases the association will refer to a User, so we need :class_name => ‘User’

1
2
3
4
class Sale < ActiveRecord::Base
  belongs_to :buyer, :class_name => 'User', :foreign_key => 'buyer_id'
  belongs_to :seller, :class_name => 'User', :foreign_key => 'seller_id'
end

You don’t actually need the foreign key in this case (since the default is association name + _id (from rails 2.0) so this simplifies down to

1
2
3
4
class Sale < ActiveRecord::Base
  belongs_to :buyer, :class_name => 'User'
  belongs_to :seller, :class_name => 'User'
end

Easy! Now we just need to do the other side of the association. A user will have many sales as a seller and many sales as a buyer, we’ll call the associations sales and purchases. Like before, the association name determines the method you call to get/set an association. ActiveRecord assumes the defaults in much the same way, so from

1
2
3
class User < ActiveRecord::Base
  has_many :sales
end

ActiveRecord infers that the class is Sale (overridable by the :class_name option). The foreign key inference is different. Since we’re talking creating assocations on the User class, ActiveRecord assumes that database columns pointing at this table are called user_id (underscorize the class name and add id [3]). So the above is equivalent to

1
2
3
class User < ActiveRecord::Base
  has_many :sales, :class_name => 'Sale', :foreign_key => 'user_id'
end

Our associations are called sales and purchases, both refer to things of class Sale and the foreign keys are buyer_id and seller_id, so our User class should look like

1
2
3
4
class User < ActiveRecord::Base
  has_many :purchases, :class_name => 'Sale', :foreign_key => 'buyer_id'
  has_many :sales, :class_name => 'Sale', :foreign_key => 'seller_id'
end

If you want you can drop the :class_name option from the sales association, it’s not needed. There we go, you are now an association master! The same ideas apply in the other cases where the defaults aren’t write. Always make the association name unique (try and come up with a good name, it will really help the readability of your code), and then set foreign_key and class_name options appropriately. I haven’t talked about has_many :through at all, but that’s because Josh Susser already has a lovely writeup of that over here.

[1]This is a slight lie. ActiveRecord does very little when you call belongs_to, has_many etc… all these things are computed when they are needed. That distinction isn’t really important here.

[2]ActiveRecord use to base this on the :class_name option, ie the default key was (basically) the lowercased class name followed by _id. This changed in rails 2.0

[3]There’s a bit more to this in cases where your models are in modules and so on. The gory details are in Inflector#foreign_key