The Sinatra track on Learn.co introduced us to the powerful – and almost magical – ActiveRecord ORM. ActiveRecord (AR) association methods simplify the process of creating the right links between our models by handling a lot of work behind the scenes. This allows us to focus on more important aspects of our apps, such as the logic and behavior. However, these same association methods can become even more powerful once we start associating them with options.
Through the use of these options, we can come up with complex associations that give us access to more methods than we would otherwise have. All without needing to add too many extra models and/or extra tables to our app and database.
In this blog post, I hope to explain (while at the same time test my own understanding) of aliasing active record associations, and the use of options such as :class_name, :foreign_key, :through, and :source.
The domain we will be using contains User,Post and Comment models. Assume their associated tables have been migrated and the schema looks like this:

Let’s get started!
A post belongs to a user, and a user has many posts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/post.rb | |
| class Post < ActiveRecord::Base | |
| belongs_to :user | |
| end | |
| # app/models/user.rb | |
| class User < ActiveRecord::Base | |
| has_many :posts | |
| end |
If we try to create an association between user and post, it won’t work:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| post = Post.create(title: "Best Post Evaaaar!") | |
| user = User.create(name: "Someone Important") | |
| post.user = user | |
| #=> can't write unknown attribute 'user_id' |
So, let’s try to fix the association in Post:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/post.rb | |
| class Post < ActiveRecord::Base | |
| belongs_to :author | |
| end | |
| # However, this now leads us to another error when we try the following: | |
| post = Post.first # returns first record from posts table | |
| user = User.first # returns first record from users table | |
| post.author = user | |
| #=> NameError: uninitialized constant Post::Author |
Using the option :class_name
We can tell it what we are referring to with the option :class_name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/post.rb | |
| class Post < ActiveRecord::Base | |
| # alias the user association as "author" | |
| belongs_to :author, class_name: "User" | |
| end |
We are letting Rails know that when we call .author on a post, we want the return to be an instance of User. The above is what is referred to as an aliased association.
OK. But Why?
The beauty of aliasing as we did above, is that we don’t need to define a separate class – for example, Author and then have it inherit from User . We also don’t need another table in our database.
Now in Post, we want to make sure the User (aka, Author) has many authored_posts as an author . So we have to let the User model know what foreign key to use to find the right posts.
As you may have guessed, we accomplish that by using the :foreign_key option…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/post.rb | |
| class User < ActiveRecord::Base | |
| has_many :authored_posts, class_name: "Post", foreign_key: :author_id | |
| end | |
| # The above is an example of an aliased has_many association | |
| # And thanks to it, we can now do the following: | |
| jk_rowling = User.find_by(name: "J.K. Rowling") | |
| jk_rowling.authored_posts | |
| #=> [collection of posts by this particular author] |
With the above we are telling Rails to go to the posts table and retrieve all posts that are associated with this user id, but to make sure to look for the id in the column named “author_id”.
The norm is for the foreign_key in the has_many table to be named after the model that it belongs_to. This means a user would be looking for “user_id” in the authored_posts table.
Note that more we use aliases, the more explicit we have to be with telling AR how things are actually associated.
Now let’s look at the Comment model, which on top of containing content, will act as a join table.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/comment.rb | |
| class Comment < ActiveRecord::Base | |
| belongs_to :commentor, class_name: "User", foreign_key: :user_id | |
| belongs_to :post | |
| end | |
| # Without adding the foreign_key: :user_id we get the following error | |
| # when trying to set a user as a comment’s commentor: | |
| jk_rowling = User.first | |
| comment = Comment.create(content: "Still 10 more books left in the Harry Potter universe!!!") | |
| comment.commentor = jk_rowling | |
| #=> ActiveModel::MissingAttributeError: can't write unknown attribute 'comment_id' |
In order to complete the association between User and Comment, we need to say that a User has many Comment(s)…
Using the option :through
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/user.rb | |
| class User < ActiveRecord::Base | |
| has_many :authored_posts, class_name: "Post", foreign_key: :author_id | |
| has_many :comments | |
| has_many :commented_posts, through: :comments | |
| end |
With that last addition, we can now say jk_rowling.comments, which will return a collection (array) containing all of the comments left by J.K. Rowling herself.
But what happens if we want to know about all the posts where J.K Rowling has left a comment?
As it currently stands, calling .commented_posts on an instance of User, results in an ActiveRecord error:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| jk_rowling.commented_posts | |
| #=> ActiveRecord::HasManyThroughSourceAssociationNotFoundError: | |
| # Could not find the source association(s) "commented_post" or :commented_post in model Comment. | |
| # Try 'has_many :commented_posts, through => :comments, source => <name>'. Is it one of commentor or post? |
Look at that! ActiveRecord is nice enough to let us know where we are going wrong. It is trying to figure out which of the two models that a Comment belongs_to (commentor or post) represents a commented_post. What is the source of this association?
The :source Option
In order to fix the error, we have to do the following:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/user.rb | |
| class User < ActiveRecord::Base | |
| has_many :authored_posts, class_name: "Post", foreign_key: :author_id | |
| has_many :comments | |
| has_many :commented_posts, through: :comments, source: :post | |
| end |
We can read that last relationship as a user has many commented posts, through comments and the existing relationship between comments and posts.
With the above, we have basically created an extra join table, even though we didn’t hard code a join table for commented_posts into our database. However, because of the way the relationships are set up between these models we get to reap the benefits of one. What kind of magic is this!?
Another benefit of aliasing like we did above, is that we get to decide what we want to call this virtual join table. We can make sure our code still reads well.
Now we just need to complete the relationship that a post has many comments and many commentors by adding the following to Post:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # app/models/post.rb | |
| class Post < ActiveRecord::Base | |
| belongs_to :author, class_name: "User" | |
| has_many :comments | |
| has_many :commentors, through: :comments, source: :commentor | |
| end |
post.commentorsConclusion
has_many :commented_posts, :through => :comments, :source => :post


















