Understanding :source option of has_one/has_many through of Rails

Let me expand on that example:

class User
  has_many :subscriptions
  has_many :newsletters, :through => :subscriptions
end

class Newsletter
  has_many :subscriptions
  has_many :users, :through => :subscriptions
end

class Subscription
  belongs_to :newsletter
  belongs_to :user
end

With this code, you can do something like Newsletter.find(id).users to get a list of the newsletter's subscribers. But if you want to be clearer and be able to type Newsletter.find(id).subscribers instead, you must change the Newsletter class to this:

class Newsletter
  has_many :subscriptions
  has_many :subscribers, :through => :subscriptions, :source => :user
end

You are renaming the users association to subscribers. If you don't provide the :source, Rails will look for an association called subscriber in the Subscription class. You have to tell it to use the user association in the Subscription class to make the list of subscribers.


Sometimes, you want to use different names for different associations. If the name you want to use for an association on the model isn't the same as the assocation on the :through model, you can use :source to specify it.

I don't think the above paragraph is much clearer than the one in the docs, so here's an example. Let's assume we have three models, Pet, Dog and Dog::Breed.

class Pet < ActiveRecord::Base
  has_many :dogs
end

class Dog < ActiveRecord::Base
  belongs_to :pet
  has_many :breeds
end

class Dog::Breed < ActiveRecord::Base
  belongs_to :dog
end

In this case, we've chosen to namespace the Dog::Breed, because we want to access Dog.find(123).breeds as a nice and convenient association.

Now, if we now want to create a has_many :dog_breeds, :through => :dogs association on Pet, we suddenly have a problem. Rails won't be able to find a :dog_breeds association on Dog, so Rails can't possibly know which Dog association you want to use. Enter :source:

class Pet < ActiveRecord::Base
  has_many :dogs
  has_many :dog_breeds, :through => :dogs, :source => :breeds
end

With :source, we're telling Rails to look for an association called :breeds on the Dog model (as that's the model used for :dogs), and use that.