Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Rails Associations

DZone's Guide to

Rails Associations

In this post, we take a look at associations such as one-to-one, many-to-many, and one-to-many, and how they can be applied to Ruby on Rails applications.

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

An association is a connection between two Active Record models. It makes it 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 the 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 an 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's 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 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

Aditional 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 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!

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
web dev ,ruby ,ruby on rails ,web application development

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}