Brush up Your Knowledge of Rails Associations

Share this article

Brush up Your Knowledge of Rails Associations
relational database data table related symbol vector illustration concept flat

Rails 5 is coming very soon (it’s currently at RC1), so while preparing for this major version, it is high time to revisit the basics. Today we are going to discuss ActiveRecord associations.

Associations make it much easier to perform various operations on the records in your code. There are multiple types of associations available:

  • One-to-one
  • One-to-many
  • Many-to-many
  • Polymorphic one-to-many

In this article we are going to discuss all of them as well as some options for further customization. The source code is available on GitHub.

One-to-many Associations

To start off, create a new Rails app:

$ rails new OhMyRelations -T

For this demo Rails 5 release candidate is used, but everything discussed in this article applies to Rails 3 and 4.

A one-to-many association is probably the most common and widely used type. The idea is pretty simple: record A may have many records B and record B belongs to only one record A. For each record B you have to store an id of the record A it belongs to – this id is called a foreign key.

Let’s try it in practice. Suppose, we have a user that may have a bunch of posts. First of all, create the User model:

$ rails g model User name:string

As for the posts table, it has to contain a foreign key and the convention is to name this column after the related table, so in our case that would be user_id (note the singular form):

$ rails g model Post user:references body:text

user:references is a neat way to define a foreign key – it will automatically name the corresponding column user_id and add an index on it. Inside your migration you’ll see something like:

t.references :user, foreign_key: true

Of course, you can also say:

$ rails g model Post user_id:integer:index body:text

Apply your migrations:

$ rake db:migrate

Don’t forget that the models themselves have to be equipped with the special methods to properly establish relations. As long as we used the references keyword when generating migration, the Post model will already have the following line:

belongs_to :user

Still, the User has to be modified manually:

models/user.rb

[...]
has_many :posts
[...]

Note the plural form (“posts“) for the relation’s name. For the belongs_to relation, you use the singular form (“user”).

That wasn’t that hard, was it? Now the relation is established and you can use methods like:

  • user.posts – references user’s posts
  • user.posts << post – establishes a new relation between a user and a post
  • post.user – references an owner of the post
  • user.posts.build({ }) – instantiates a new post for the user, but doesn’t save it into the database yet. It does populate the user_id attribute on the post. This is similar to saying Post.new({user_id: user.id}).
  • user.posts.create({ }) – creates a new post and saves it into the database.
  • post.build_user – same as above, instantiates a new user without saving it
  • post.create_user – same as above, instantiates and saves the user into the database

Let’s discuss some options that you may set when defining relations. Suppose, for example, that you want the belongs_to relation to be called author, not user:

models/post.rb

[...]
belongs_to :author
[...]

This, however, is not enough, because Rails uses the :author argument to derive the name of the associated model and the foreign key. As long as we don’t have a model called Author, we have to specify the actual name of the class:

models/post.rb

[...]
belongs_to :author, class_name: 'User'
[...]

So far so good, but the posts table doesn’t have an author_id field as well, so we need to redefine the :foreign_key option:

models/post.rb

[...]
belongs_to :author, class_name: 'User', foreign_key: 'user_id'
[...]

Now, inside your console you may do something like:

$ post = Post.new
$ post.create_author

and everything should be working just fine.

Please note that, for the has_many association, there are also :class_name and :foreign_key options available. What’s more, using these options, you can establish a relation where a model references itself as described here.

Another common option that you can set is :dependent, usually for the has_many relation. Why do we need it? Suppose, a user called John has a bunch of posts. Then, out of the blue, John is deleted from the database – well, that happens… But what about his posts? They still have the user_id column set to John’s id, but this record does not exist anymore! These posts are called orphaned records and may lead to various problems, so you probably will want to take care of such a scenario.

The :dependent option accepts the following values:

  • :destroy – all associated objects will removed one by one (in a separate query). The appropriate callbacks will be run before and after deletion.
  • :delete_all – all associated objects will be deleted in a single query. No callbacks will be executed.
  • :nullify – foreign keys for the associated objects will be set to NULL. No callbacks will be executed.
  • :restrict_with_exception – if there are any associated records, an exception will be raised.
  • :restrict_with_error – if there are any associated records, an error will be added to the owner (the record you are trying to delete).

So, as you can see, there are many ways to handle this scenario. For this demo I’ll use :destroy:

models/user.rb

[...]
has_many :posts, dependent: :destroy
[...]

What’s interesting is, belongs_to also supports the :dependent option – it may be set to either :destroy or :delete). However, with one-to-many relations I highly discourage you from setting this option.

Another thing worth mentioning is that in Rails 5, by default, you cannot create a record if its parent does not exist. Basically, this means that you can’t do:

Post.create({user_id: nil})

because obviously there is no such user.

This new feature may be disabled for the whole app by tweaking the following initializer file:

config/initializers/active_record_belongs_to_required_by_default.rb

Rails.application.config.active_record.belongs_to_required_by_default = false # default is true

Also you may set the :optional setting for the individual relations:

belongs_to :author, optional: true

One-to-one Associations

With one-to-one relations you are basically saying that a record contains exactly one instance of another model. For example, let’s store user’s address in a separate table called addresses. This table must contain a foreign key which is by default named after the relation:

$ rails g model Address street:string city:string country:string user_id:integer:index
$ rake db:migrate

For the user simply say:

models/user.rb

has_one :address

Having this in place, you may call methods like

  • user.address – fetches the related address
  • user.build_address – same as the method provided by belongs_to; instantiates a new address, but does not save it into the database.
  • user.create_address – instantiates a new address, and saves it into the database.

The has_one relation allows you to define :class_name, :dependent, :foreign_key, and other options, just like with has_many.

Many-to-many Associations

“Has and Belongs to Many” Association

Many-to-many associations are a bit more complex and can be set up in two ways. First of all, let’s discuss a direct relation, without any intermediate models. It is called “has and belongs to many” or simply “HABTM”.

Suppose, a user can enroll in many different events and an event may contain many users. To achieve this goal, we require a separate table (often called a “join table”) that stores relations between users and events. This table has to have a special name: users_events. Basically, it is just a combination of the two tables’ names that we are establishing relation between.

Firstly, create events:

$ rails g model Event title:string

Now the intermediate table:

$ rails g migration create_events_users user:references event:references
$ rake db:migrate

Note the name of the intermediate table – Rails expects it to be composed of two tables’ names (`events` and `users`). Moreover, the higher-order name (`events`) should be the first one (`events > users`, because letter “e” precedes letter “u”).The last step is to add the has_and_belongs_to_many to both models:

model/user.rb

[...]
has_and_belongs_to_many :events
[...]

model/event.rb

[...]
has_and_belongs_to_many :users
[...]

Now you may call methods like:

  • user.events
  • user.events << [event1, event2] – creates relations between a user and a bunch of events
  • user.events.destroy(event1) – destroys relation between records (the actual records won’t be removed). There is also a delete method that does pretty much same except it does not run callbacks
  • user.event_ids – a neat method that returns an array of ids from the collection
  • user.event_ids = [1,2,3] – makes the collection contain only the objects identified by the supplied primary key values. Note that if the collection initially contained other objects, they will be removed.
  • user.events.create({}) – creates a new object and adds it to the collection

has_and_belongs_to_many accepts :class_name and :foreign_key options that we’ve already discussed. However, it also supports some other special settings:

  • :association_foreign_key – by default, Rails uses the relation name to find the foreign key in the intermediate table that is, in turn, used to find the associated object. So, for example, if you say has_and_belongs_to_many :users, the user_id column will be used. However, that’s not always convenient, so the :association_foreign_key can be employed to define a custom column’s name.
  • :join_table – this option can be used to redefine the name for the intermediate table (that is called users_events in our case).

That’s pretty much it. Still, has_and_belongs_to_many is a pretty rigid way to define many-to-many associations because you can’t work with the relationship model independently. In many cases, you’ll want to store some additional data for each relation or define extra callbacks which cannot be done with HABTM relation. Therefore, I am going to show you another, more convenient, way to solve the same task.

“Has Many Through” Association

Another way to define a many-to-many association is to use the has many through association type. Suppose we have a list of games and, from time to time, competitions on these games are held. Many users may participate in many competitions. Apart from establishing a many-to-many relation between the users and games, we also want to store additional info about each enrollment, like the competition’s category (amateur, semi-pro, pro, etc.)

First of all, create a new Game model:

$ rails g model Game title:string

We also need an intermediate table, but this time with a model:

$ rails g model Enrollment game:references user:references category:string
$ rake db:migrate

For the Enrollment model everything was set up automatically:

models/enrollment.rb

[...]
belongs_to :game
belongs_to :user
[...]

Tweak the two other models:

models/user.rb

[...]
has_many :enrollments
has_many :games, through: :enrollments
[...]

models/game.rb

[...]
has_many :enrollments
has_many :users, through: :enrollments
[...]

Here, we explicitly specify the intermediate model to establish this relation. Now you can work with each enrollment as an independent entity, which is more convenient. Note, that if the name of the source association cannot be automatically inferred from the association’s name, you may utilize the :source option and set its value accordingly.

So, to summarize using has_many :through is preferred over has_and_belongs_to_many, however, in simple cases you may stick with the latter solution.

“Has One Through” Associations

Another interesting association type is the has one through that is somewhat similar to what we’ve seen in the previous section. The idea is that a model is matched with another one through an intermediate model. Suppose a user has a purse and a purse has a payment history. First of all, create the Purse model:

$ rails g model Purse user:references funds:integer

user_id is going to be the foreign key to establish relation between a user and his purse. And now the PaymentHistory model:

$ rails g model PaymentHistory purse:references
$ rake db:migrate

Now tweak models like this:

models/user.rb

has_one :purse
has_one :payment_history, through: :purse

models/purse.rb

belongs_to :user
has_one :payment_history

models/payment_history.rb

belongs_to :purse

This type of relation is rarely used, but sometimes can come in handy.

Polymorphic Associations

Polymorphic associations may save the day in certain scenarios. Despite the scary name, the idea is simple: you have a model that may belong to many different models on a single association. Suppose, you are going to make games and users commentable. Of course, you might have two separate models called UserComment and GameComment but, all in all, comments are very similar except for the fact that they belong to different models. That’s when polymorphic associations come into play.

Create a new Comment model:

$ rails g model Comment body:text commentable_id:integer:index commentable_type:string

Probably, you are already getting the idea. commentable_id is a foreign key to establish relation with other tables. commentable_type, in turn, will contain the actual name of a model to which a comment belongs. The migration:

create_table :comments do |t|
  t.text :body
  t.integer :commentable_id
  t.string :commentable_type

  t.timestamps
end
add_index :comments, :commentable_id

can be re-written as:

create_table :comments do |t|
  t.text :body
  t.references :commentable, polymorphic: true, index: true

  t.timestamps
end
add_index :comments, :commentable_id

We’ve already seen the references method before, but this time it also comes with a :polymorphic option.

Apply the migration:

$ rake db:migrate

The Comment model is going to have the belongs_to association, but with a small twist:

models/comment.rb

[...]
belongs_to :commentable, polymorphic: true
[...]

As long as we called our fields :commentable_id and :commentable_type, the whole relation has to be called commentable.

Now the User and Game models:

models/user.rb

[...]
has_many :comments, as: :commentable
[...]

models/game.rb

[...]
has_many :comments, as: :commentable
[...]

:as is a special option explaining that this is a polymorphic association. Now, boot your console and try running:

$ u = User.create
$ u.comments.create({body: 'test'})

Inside the comments table, commentable_type will be set to User and commentable_idto the user’s id. Your polymorphic association works great and now you can easily make other models commentable!

Conclusion

In this article, we’ve discussed various types of associations available in Rails. We’ve seen how to set them up and further customize. Official documentation contains a nice reference for each association, so be sure to check it out if you haven’t already done so.

Hopefully, this article was a useful brush-up of your knowledge. Stay tuned and see you soon!

Frequently Asked Questions (FAQs) on Rails Associations

What are the different types of Rails Associations?

Rails Associations are used to establish a connection between different models. There are four main types of associations in Rails: ‘belongs_to’, ‘has_one’, ‘has_many’, and ‘has_many :through’. The ‘belongs_to’ and ‘has_one’ associations are used when a model has a one-to-one connection with another model. The ‘has_many’ association is used when a model has a one-to-many connection with another model. The ‘has_many :through’ association is used when a model has a many-to-many connection with another model.

How do I set up a ‘belongs_to’ association in Rails?

To set up a ‘belongs_to’ association, you need to declare the association in the model. For example, if you have a model ‘Book’ that belongs to a ‘Category’, you would write ‘belongs_to :category’ in the ‘Book’ model. This tells Rails that each book belongs to a single category.

What is the difference between ‘has_one’ and ‘has_many’ associations?

The main difference between ‘has_one’ and ‘has_many’ associations is the number of associated objects. A ‘has_one’ association indicates that one model object is associated with one other model object. On the other hand, a ‘has_many’ association indicates that one model object can be associated with many other model objects.

How do I use the ‘has_many :through’ association?

The ‘has_many :through’ association is used when you need to work with two models that have a many-to-many relationship. For example, if you have a ‘Student’ model and a ‘Course’ model, and each student can enroll in many courses, and each course can have many students, you would use a ‘has_many :through’ association. You would need to create a third model, say ‘Enrollment’, which would belong to both ‘Student’ and ‘Course’. Then, in the ‘Student’ and ‘Course’ models, you would write ‘has_many :enrollments’ and ‘has_many :courses, through: :enrollments’ or ‘has_many :students, through: :enrollments’ respectively.

How do I create a self-join association in Rails?

A self-join association is when a model needs to have a relationship with itself. For example, if you have a ‘User’ model, and each user can have many friends who are also users, you would use a self-join association. You would need to create a second model, say ‘Friendship’, which would belong to two instances of the ‘User’ model. Then, in the ‘User’ model, you would write ‘has_many :friendships’ and ‘has_many :friends, through: :friendships’.

How do I handle polymorphic associations in Rails?

Polymorphic associations in Rails allow a model to belong to more than one other model, on a single association. For example, you might have a ‘Picture’ model that belongs to either a ‘Product’ model or a ‘User’ model. In the ‘Picture’ model, you would write ‘belongs_to :imageable, polymorphic: true’. Then, in the ‘Product’ and ‘User’ models, you would write ‘has_many :pictures, as: :imageable’.

How do I use the ‘has_and_belongs_to_many’ association?

The ‘has_and_belongs_to_many’ association is used when you need to create a direct many-to-many connection between two models, without needing a third model. For example, if you have a ‘Book’ model and an ‘Author’ model, and each book can have many authors, and each author can write many books, you would use a ‘has_and_belongs_to_many’ association. In both the ‘Book’ and ‘Author’ models, you would write ‘has_and_belongs_to_many :authors’ or ‘has_and_belongs_to_many :books’ respectively.

How do I handle nested associations in Rails?

Nested associations in Rails allow you to work with attributes from associated models within the parent model. For example, if you have a ‘User’ model that has many ‘Posts’, and each post has many ‘Comments’, you can access the comments for a user’s post directly from the ‘User’ model. You would need to use the ‘accepts_nested_attributes_for’ method in the ‘User’ model, like so: ‘accepts_nested_attributes_for :posts, allow_destroy: true’.

How do I use the ‘dependent’ option in Rails associations?

The ‘dependent’ option in Rails associations allows you to specify what should happen to the associated objects when the owner object is destroyed. For example, if you have a ‘User’ model that has many ‘Posts’, and you want all of a user’s posts to be destroyed when the user is destroyed, you would write ‘has_many :posts, dependent: :destroy’ in the ‘User’ model.

How do I validate associated objects in Rails?

You can validate associated objects in Rails by using the ‘validates_associated’ method. For example, if you have a ‘Book’ model that belongs to a ‘Category’, and you want to ensure that a book is only valid if its associated category is valid, you would write ‘validates_associated :category’ in the ‘Book’ model.

Ilya Bodrov-KrukowskiIlya Bodrov-Krukowski
View Author

Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.

GlennGRuby on Rails
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week