Has Many Through and Has and Belongs to Many in Rails 5

Has and Belongs to Many Association

You can watch this tutorial as a screencast Has Many Through and Has and Belongs to Many in Rails 5.

Step 1

Create a category model with name attribute.

rails g model category name

In Rails 5, if you don't provide the column type, it defaults to string. As you can see it in the generated migration file:

class CreateCategories < ActiveRecord::Migration[5.0]
  def change
    create_table :categories do |t|
      t.string :name

      t.timestamps
    end
  end
end

Step 2

Create a product model with name attribute.

rails g model product name

Here is the generated Migration file for products.

class CreateProducts < ActiveRecord::Migration[5.0]
  def change
    create_table :products do |t|
      t.string :name

      t.timestamps
    end
  end
end

Rails 5 creates the attribute with string as the default type.

Step 3

For the has_and_belongs_to_many association, let's create a join table.

rails g migration CreateJoinTableCategoryProduct category product

If you use CreateJoinTableTableName1TableName2 model1 model2 for migration, Rails 5 migration generator will automatically create the migration file with the appropriate join table with index like this:

class CreateJoinTableCategoryProduct < ActiveRecord::Migration[5.0]
  def change
    create_join_table :categories, :products do |t|
      t.index [:category_id, :product_id]
    end
  end
end

Step 4

Migrate the database:

rails db:migrate

In schema.rb, you can see the index and the categories_products join table:

ActiveRecord::Schema.define(version: 20160329172233) do
  create_table "categories", force: :cascade do |t|
    t.string   "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "categories_products", id: false, force: :cascade do |t|
    t.integer "category_id", null: false
    t.integer "product_id",  null: false
  end

  add_index "categories_products", ["category_id", "product_id"], name: "index_categories_products_on_category_id_and_product_id"

  create_table "products", force: :cascade do |t|
    t.string   "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

You can also see the index defined for the categories_products table.

Step 5

Declare the has_and_belongs_to_many in the product model.

class Product < ApplicationRecord
  has_and_belongs_to_many :categories
end

Declare the has_and_belongs_to_many in the category model.

class Category < ApplicationRecord
  has_and_belongs_to_many :products
end

Has Many Through

Step 1

Create a categorization join model.

rails g model categorization product_id:integer category_id:integer position:integer

The migration file looks like this:

class CreateCategorizations < ActiveRecord::Migration[5.0]
  def change
    create_table :categorizations do |t|
      t.integer :product_id
      t.integer :category_id
      t.integer :position

      t.timestamps
    end

    add_index :categorizations, [:product_id, :category_id]
  end
end

To find the abstraction, think about a concept that ends in tion, ship or ment.

Step 2

Migrate the database:

rails db:migrate

Step 3

You can see the structure of the tables in the schema.rb.

ActiveRecord::Schema.define(version: 20160329173814) do

  create_table "categories", force: :cascade do |t|
    t.string   "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "categories_products", id: false, force: :cascade do |t|
    t.integer "category_id", null: false
    t.integer "product_id",  null: false
  end

  add_index "categories_products", ["category_id", "product_id"], name: "index_categories_products_on_category_id_and_product_id"

  create_table "categorizations", force: :cascade do |t|
    t.integer  "product_id"
    t.integer  "category_id"
    t.integer  "position"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
  end

  add_index "categorizations", ["product_id", "category_id"], name: "index_categorizations_on_product_id_and_category_id"

  create_table "products", force: :cascade do |t|
    t.string   "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

We have categorizations table with an index defined for all the foreign keys it it.

Step 4

Declare the belongs_to association in the Categorization model.

class Categorization < ApplicationRecord
  belongs_to :product
  belongs_to :category
end

Declare the has_many_through declarations in the product and category models.

class Product < ApplicationRecord
  has_many :categorizations
  has_many :categories, through: :categorizations
end
class Category < ApplicationRecord
  has_many :categorizations
  has_many :products, through: :categorizations
end

Summary

In this article, you learned how to use has_and_belongs_to_many and has_many through associations in Rails 5.


Related Articles

Watch this Article as Screencast

You can watch this as a screencast Has Many Through and Has and Belongs to Many in Rails 5


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.