The simplest way to understand Associations in Rails

Sharma'arke Ahmed
May 18, 2020 · 9 min read
Image for post
Image for post

As someone who’s new to Rails with no prior experience, I’d like to share with you how I came to understand this tricky concept when developing Rails applications. I’ll go over some keywords that you might have seen and tried to use and give examples on how to effectively use Rails Associations.

Overview on Rails Associations

Associations are basically defining the relationship between models. When speaking of models, we should go under the hood and understand what the code you are writing does.

Models in Rails are SQL tables with your typical columns, primary key, and foreign key. If you understand SQL, this part should be simple enough for you to understand.

In Associations, we are executing either a JOIN SQL query (if there’s a through-table) or a normal SELECT query. Look at the code below for example:

These two lines of code do exactly the same thing, the Rails code is being turned into its equivalent SQL query and then the SQL query is executed normally like you’d do when working with PHP for example and you are trying to select some data from your MySQL database. Rails is different from PHP as it gives you helper methods that execute SQL queries for you. This is where the concept of Associations comes into play.

When implementing Associations, you must have a clear idea of the relationships between your models. Is it a one-to-one, one-to-many, or many-to-many? Don’t worry, this part only requires you to have general knowledge rather than technical knowledge. Knowing the type of relationship can help you out tremendously later on.

Image for post
Image for post
Entity Relationship Diagram (ERD) Cardinality

Figure out the relationship

Ask yourself these questions when designing your model. We’ll take a TV subscription as an example.

  1. How many subscriptions can a User have?
  2. How many Users can a subscription have?

A User pays a monthly or yearly subscription for their TV. Every time their subscription expires, they must renew it in order to keep watching their TV. So a new subscription is made for the User every time the current one expires. What does this mean? A User can have multiple subscriptions, which answers our first question, the User has many subscriptions.

A new subscription is created every time a certain User’s current subscription expires. Can a User who paid for this subscription be available only for the User who paid for it or multiple Users? In certain situations, you could say the latter, but at the end of the day, it depends on your needs. For simplicity, we’ll go for the traditional way, the subscription is only available to one User which answers our second question, a subscription has one User.

After our little investigation, we have come to the conclusion that our two models, Users, and Subscriptions, have a one-to-many relationship.

Image for post
Image for post

Implementing your Associations

After you’ve set up your Rails Models, your User and Subscription models would look like this:

class User < ApplicationRecordendclass Subscription < ApplicationRecordend

To start implementing Associations, you should make sure your SQL tables are also related to each other. This will help with maintaining referential integrity from the database side.

For this example, your subscription table should have a user_id column which will be a foreign key. This foreign key can be added by creating the following migration:

rails g migration AddUserToSubscriptions user:references

This will come in handy for you later on and will not cause you any trouble. Don’t forget to run the migration afterward.

After the migration, we should specify in the models what its relationship is to the other model. Have a look at the updated code for the two models:

class User < ApplicationRecordhas_many :subscriptionsendclass Subscription < ApplicationRecordbelongs_to :userend

You’ll notice that User has_many Subscriptions, which we established before and Subscription belongs_to a User. Now here’s where I got a little lost the first time I tried Associations. Have a look at the list below:

  1. has_many
  2. has_one
  3. belongs_to
  4. has_many :through
  5. has_one :through
  6. has_and_belongs_to_many

These are the types of Associations you’d use to state the relationship between any number of models. In this example, we said that User and Subscription has a one-to-many relationship. From the Subscription side, a subscription has a connection to only one User, so naturally, we’d use the belongs_to Association as it “belongs to one instance of the other model”. From the User side, a user can have a connection to multiple subscriptions, so we’d use the has_many association as it “has zero or more instances of another model”.

To make things easier, you can look at the types of Association this way:

  1. has_many + belongs_to = one-to-many relationship
  2. has_one + belongs_to = one-to-one relationship
  3. has_many :through + belongs_to = many-to-many relationship
  4. has_one :through + belongs_to = one-to-one relationship
  5. has_and_belongs_to_many = many-to-many relationship

Now you’re probably noticing the :through keyword that is being used by has_many and has_one. This is used when there’s a third model (table) involved, which brings us to our next example, implementing Rails Associations for a many-to-many relationship. We’ll stick to the original example but with a little tweak. Say you wanted your customers to be able to share a subscription, how would you model this?

A third table is required in order to accommodate for this. This third table is what you call a through-table. Have a look below at the tables:

Image for post
Image for post

We added a Shared Subscription table which will be used as a through-table. This table usually contains only foreign keys but you can add extra columns if needed.

Now we need to again update our models as well as add a new model:

class User < ApplicationRecordhas_many :created_subs, foreign_key: :holder_id, class_name: 'Subscription'
has_many :sharedsubscriptions, foreign_key: :sub_user_id
has_many :accessable_subs, through: :sharedsubscriptions, source: :shared_sub
endclass Subscription < ApplicationRecordbelongs_to :holder, class_name: 'User'
has_many :sharedsubscriptions, foreign_key: :shared_sub_id
has_many :allowed_users, through: :sharedsubscriptions, source: :sub_user
endclass SharedSubscriptions < ApplicationRecordbelongs_to :shared_sub, class_name: 'Subscription'
belongs_to :sub_user, class_name: 'User'
end

Let’s break it down model by model. We’ll start off with User:

class User < ApplicationRecordhas_many :created_subs, foreign_key: :holder_id, class_name: 'Subscription'
has_many :sharedsubscriptions, foreign_key: :sub_user_id
has_many :accessable_subs, through: :sharedsubscriptions, source: :shared_sub
end

In English, this model is saying that it has many created subscriptions which have a foreign key of “holder_id” which is in the Subscription table:

has_many :created_subs, foreign_key: :holder_id, class_name: 'Subscription'

When you have a custom foreign key name that is not generated by Rails, you must provide the foreign_key attribute as well as the class_name attribute in order for Rails to know which is a foreign key and where it belongs. If you do not provide a foreign_key attribute like so:

has_many :created_subs

It means that Rails will look for a table name “Created_Subs” and will then look for the foreign key column called “created_subs_id” which neither exists. class_name attribute is used to guide Rails to what table and foreign-key attribute guide Rails to what column. So keep an eye on how you write your models.

To continue, the User model is saying that it has many Shared Subscriptions:

has_many :sharedsubscriptions, foreign_key: :sub_user_id

In this case, we didn’t add the class_name attribute as it is not necessary. If you look at the SharedSubscriptions model, we’ve already told Rails where to look when the foreign key “sub_user_id” is being queried:

belongs_to :sub_user, class_name: 'User'

Again, if your foreign key name is custom and not generated by Rails, you’ll have to provide a class_name in order for Rails to know what table to look. Not mentioning class_name would look something like this:

belongs_to :sub_user

Rails will look for the Sub_User table and it will use the “sub_user_id” column in the SharedSubscription table as the foreign key. The foreign key mentioned here does exist but not the table, which would lead to head scratches and hours of nonsensical debugging. Again, as I’ve mentioned before, keep an eye on how you write your models.

Returning to the User model, the model has many subscriptions it has access to, which should be checked through the SharedSubscription table where its foreign key is shared_sub:

has_many :accessable_subs, through: :sharedsubscriptions, source: :shared_sub

The symbol ‘:accessable_subs’ will be the property you’ll use to access the collection of subscriptions that the User has access to. When using has_many, the name of the ruby symbol you provide will be a property that is part of the User model. This part is possible because you’ve stated before that the User model has_many SharedSubscriptions. Without mentioning the relationship between your normal model and the through-table model you won’t be able to generate a collection. In your controller you’d retrieve your collections like this:

user = User.find(1);
user.accessable_subs;

Just by executing that single line, Rails will execute a join query that would look like this:

SELECT  * FROM "subscriptions" INNER JOIN "sharedsubscriptions" ON "subscriptions"."id" = "sharedsubscriptions"."shared_sub_id" WHERE "sharedsubscriptions"."sub_user_id" = 1 LIMIT 1

This is the power of Associations, you can write one-liner English like code instead of long and sometimes confusing SQL queries. Just specify the relationships between the models in question and Rails will construct and execute the query for you. Nifty stuff am I right? Disclaimer: I know this may seem improbable in the real world where a User has many subscriptions of the same service (why would anyone do that?) but this is just an example to demonstrate Rails Associations.

For the Subscriptions model, it’s very similar to the User model in terms of relationship definition:

class Subscription < ApplicationRecordbelongs_to :holder, class_name: 'User'
has_many :sharedsubscriptions, foreign_key: :shared_sub_id
has_many :allowed_users, through: :sharedsubscriptions, source: :sub_user
end

The subscription model belongs to an instance of a User that can be accessed through the “:holder” property. It will execute an SQL query that will retrieve a record from the User model where the user_id is equal to the current subscription’s holder_id.

belongs_to :holder, class_name: 'User'

The subscription model has allowed_user property which retrieves the number of users that have access to this subscription.

has_many :allowed_users, through: :sharedsubscriptions, source: :sub_user

Executing the code below in your controller would again return a collection:

sub = Subscription.find(1);
sub.allowed_users;

which will execute an SQL query that looks like this:

SELECT  * FROM "users" INNER JOIN "sharedsubscriptions" ON "users"."id" = "sharedsubscriptions"."sub_user_id" WHERE "sharedsubscriptions"."shared_sub_id" = 1 LIMIT 1

Like I said before, the model’s relationship definition is very similar and when you figure out one side of the relationship, you can then mirror the same definitions on the other side of the relationship.

Lastly, the through-table SharedSubscription only tells Rails where it’s foreign keys are pointed to:

class SharedSubscriptions < ApplicationRecordbelongs_to :shared_sub, class_name: 'Subscription'
belongs_to :sub_user, class_name: 'User'
end

The shared_sub_id foreign key in the table will be the foreign key of Subscription which we’ve defined here with the class_name attribute. As I’ve said before, class_name must be provided when using custom foreign key names so that Rails knows what belongs to who.

Summary

In this article, we’ve gone through 2 examples of how we can use Rails Associations. My aim for this article was to break everything down and try to explain what goes on under the hood when working with Rails Associations. We’ve talked about how we can determine the type of relationship two models and can have and use that information to structure our models. We’ve also talked about the type of associations in Rails and in what situation to use it. Finally, we used real examples to illustrate how these relationships can be defined and what the outputs would be when executing Rails Associations code.

I hope I’ve done a good job of helping you understand Associations. I myself struggled with it for a while as it was my first time working on a back end framework similar to Rails and I’ve only been used to working with PHP before my transition to modern-day web development.

You can connect with me via LinkedIn or on Twitter.

The Startup

Medium's largest active publication, followed by +752K people. Follow to join our community.

Sharma'arke Ahmed

Written by

I’m just a full-stack dev trying to get his life together

The Startup

Medium's largest active publication, followed by +752K people. Follow to join our community.

Sharma'arke Ahmed

Written by

I’m just a full-stack dev trying to get his life together

The Startup

Medium's largest active publication, followed by +752K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store