paint-brush
Active Record Associations in Railsby@kolosek
772 reads
772 reads

Active Record Associations in Rails

by NeshaApril 29th, 2020
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

An association is a connection between two Active Record models. It makes much easier to perform various operations on the records in your code. We will divide associations into four categories: One to One, One to Many, Many to Many Polymorphic, Has Many Through and Has Belongs to Many Associations. Cover your associations with RSpec tests to make sure everything works the way you want it to. The tests for associations get more complex to write the more relations you create. You should check it out how to create your own association factories.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Active Record Associations in Rails
Nesha HackerNoon profile picture

An association is a connection between two Active Record models. It makes much easier to perform various operations on the records in your code. We will divide associations into four categories:

  1. One to One
  2. One to Many
  3. Many to Many
  4. Polymorphic One to Many

If you are new to Ruby on Rails, be sure to get up your rails project ready and check how to create models in Rails.

One to One

When you set up one-to-one relations you are saying that a record contains exactly one instance of another model. For example, if each user in your application has only one profile, you can describe your models as:

class User < ApplicationRecord       class Profile < ApplicationRecord
  has_one :profile                     belongs_to :user
end                                  end

One of the models in the given relation will have

has_one
method invocation and another one will have
belongs_to
. It is used to describe which model contains a foreign key reference to the other one, in our case, it is the profiles model.

Cover your associations with RSpec tests to make sure everything works the way you want it to.

One to Many

A one-to-many association is the most common used relation. This association indicates that each instance of the model A can have zero or more instances of another model B and model B belongs to only one model A.

Let's check it out via example. We want to create an application where users can write multiple stories, our models should look like this:

class User < ApplicationRecord       class Story < ApplicationRecord
  has_many :stories                     belongs_to :user
end                                  end

Here, deciding which model will have

has_many
and which will have
belongs_to
is more important than in one-to-one relations, because it changes the logic of your application. In both cases, the second model contains one reference to the first model in form of a foreign key.

The second model doesn't know about the first model relation to it - it is not aware if the first model has a reference to more than one of them or just to one.

The tests for associations get more complex to write the more relations you create. You should check it out how to create your own association factories to make your testing life easier.

Many to Many

Many-to-many associations are a bit more complex and can be handled in two ways, "has and belongs to many" and "has many through" relations.

Has and Belongs to Many

A

has_and_belongs_to_many
association creates a direct many-to-many connection with another model. It is simpler than the other one, as it only requires calling
has_and_belongs_to_many
from both models.

Example: Let's say, a user can have many different roles and the same role may contain many users, our models would be like this:

class User < ApplicationRecord       class Role < ApplicationRecord
  has_and_belongs_to_many :roles       has_and_belongs_to_many :users
end                                  end

You will need to create a join table for this association to work. It is a table that connects two different models. The join table is created with rails function

create_join_table :user, :role
in a separate migration.

class CreateUserRoles < ActiveRecord::Migration
  def change
    create_table :user_roles, id: false do |t|
      t.references :user, index: true, foreign_key: true
      t.references :role, index: true, foreign_key: true
    end
  end
end

This is a very simple approach, but you don't have the direct access to related objects, you can only hold references to two models and nothing else.

Has Many Through

Another way to define a many-to-many association is to use the has many through association type. Here we define a separate model, to handle that connection between two different ones.

Instead of putting down a new example, you should check this one out! It explains everything you should know about this association.

Using the example of

has_and_belongs_to_many
association, this time the three models should be written like this:

class User < ApplicationRecord
  has_many :user_roles
  has_many :roles, through: :user_roles
end
 
class UserRoles < ApplicationRecord
  belongs_to :user
  belongs_to :role
end
 
class Role < ApplicationRecord
  has_many :user_roles
  has_many :users, through: :user_roles
end

This association will enable you to do things like

user.role
and to get a list of all connected second model instances. It will also enable you to get access to data specific to the relation between first and second models.

Polymorphic

Polymorphic associations are the most advanced associations available to us. You can use it when you have a model that may belong to many different models on a single association.

Let's imagine you want to be able to write comments for users and stories. You want both models to be commentable. Here's how this could be declared:

class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end
class Employee < ApplicationRecord
  has_many :comment, as: :commentable
end
class Product < ApplicationRecord
  has_many :comment, as: :commentable
end

You can think of a polymorphic `belongs_to` declaration as setting up an interface that any other model can use. To declare the polymorphic interface you need to declare both a foreign key column and a type column in the model. You should run the migration once you are done.

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.text :body
      t.integer :commentable_id
      t.string :commentable_type
      t.timestamps
    end
 
    add_index :comments, :commentable_id
  end
end

This migration can be simplified by using references:

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.text :body
      t.references :commentable, polymorphic: true, index: true
      t.timestamps
    end
  end
end

Additional Options

There are a few additional options you can use when defining relations between models, we will cover two most commonly used ones.

class_name

If the name of the other model cannot be derived from the association name, you can use the

:class_name
option to supply the model name. You can read more about this on RailsGuides.


Example: You want the belongs_to relation to call for an author, but there are two problems:

  1. There is no model called in that way.
  2. The story table is missing an `author_id` field.

Easy fix! To make this work and stop your tests from failing, you just have to specify the

:foreign_key
and the actual name of the class:

class Story < ApplicationRecord
  belongs_to :author, class_name: 'User', foreign_key: 'user_id'
end

dependent

You can use this option when you want to get rid of orphaned records since they can lead to various problems. Orphaned records are created when we delete or destroy a model A that was associated with model B, but model B wasn't removed in the process.

class User < ApplicationRecord
  has_many :stories, dependent: :destroy
end

Example: Suppose, a user called Maria wrote a lot of stories. Then, we deleted Maria from the database, but the stories still have the user_id column set to Maria's id. These stories are called orphaned records.

Thank you for reading!

Previously published at kolosek.com/rails-associations/