Search code examples
ruby-on-railsassociationshas-many-throughmodel-associations

Please explain the has_many, through: source: Rails Association


I've found a bunch of articles, stackoverflow answers and rails documentation about 'source:', but none of it explains this association in a way I can understand it. I need the most simplified explanation of this way of associating, if possible.

My example is this:

Album:

    has_many :reviews, :dependent => :destroy
    has_many :reviewers, through: :reviews, source: :user
    belongs_to :user

Review:

    belongs_to :album, optional: true
    belongs_to :user

User:

    has_many :reviews
    has_many :reviewed_albums, through: :reviews, source: :album
    has_many :albums

The rest of the code does not mention "reviewers" or "reviewed_albums", so that is the part I understand the least.

Are those names completely irrelevant?


Solution

  • TL;DR

    source is standing for the table that this association is referring to, since we give it a different name than just .users because we already have the belongs_to :user association.

    Long explanation

    I think it's easiest with this little picture which is basically the database schema for the models you posted above.

    database schema

    We have albums, that belong to users, meaning that a user is basically someone who creates an album. We also have reviews and they belong to albums, meaning an album can be reviewed. And a review is made by a user so that's why a review belongs to a user.

    Now associations in rails is a way to create methods that can be called on a database record to find its associated record.

    We could get a user from an album or all the reviews a user made for example.

    album = Album.find(1)
    album.user # => returns the creator of the album
    
    user = User.first
    user.reviews # => returns all the reviews a user made
    

    Now there is even more connections between those models than the ones mentioned above.

    Let's look at album first:

    # album.rb
    has_many :reviews, :dependent => :destroy
    belongs_to :user
    has_many :reviewers, through: :reviews, source: :user
    

    An album belongs to one user who created it. It has many reviews. And, if we we follow the line from albums to reviews and then further along to the users table, we see that we can also access the users that gave the reviews. So we would want to do something like

    album.reviews.users
    

    Meaning: give me all the users that left a review for this album. Now this line of code wouldn't work - because album.reviews returns an array (an ActiveRecord::Relation object to be exact) and we cannot just call .users on this. But we can have another association

    has_many :reviewers, through: :reviews, source: :user
    

    And here we're calling it reviewers to not get confused with the method/association .user that refers to the creator. Normally, Rails would refer the database table name from the name of the association. Since we're giving a different name here, we have to explicitly give the name of the DB table we're referring to and that is the users table.

    So this is what this line is about - we create another association, we don't want the direct line (see image) between album and user, we want the users that left a review on this album, so we go through the reviews table and then we have to give the name (source) of the table so Rails knows in which table to look.

    And this will finally allow us to write code like this:

    album = Album.first
    album.user # => creator of the album
    album.reviewers # => all users that have left a review for this album
    

    Hope that helps! Let me know if you have any more questions. Maybe you can explain the other association with source in the users model in the comments.