DEV Community

Kartikey Tanna
Kartikey Tanna

Posted on • Originally published at kartikey.dev on

Many-to-many self join in Rails

Let’s say we have “products” and we want to prepare “kits” of those products. Kits are nothing but the group of the products.

We can use many-to-many relationship here because products can be in many kits, and kits can be associated with many products.

Also, since the kits are just grouping the products, we can use self-joins. There are multiple ways we can implement self-joins.

Using has_and_belongs_to_many

You can read more about has_and_belongs_to_many on Rails docs.

Migration

class CreateJoinTableProductKits < ActiveRecord::Migration[6.0]
  def change
    create_table :product_kits, id: false do |t|
      t.references :product, null: false, foreign_key: true, index: false
      t.references :kit, null: false, foreign_key: { to_table: :products }, index: false
      t.index [:product_id, :kit_id], unique: true
    end
  end
end

Enter fullscreen mode Exit fullscreen mode

Model

class Product < ApplicationRecord
  has_and_belongs_to_many :kits,
    join_table: :product_kits,
    class_name: 'Product',
    association_foreign_key: 'kit_id'
end

Enter fullscreen mode Exit fullscreen mode

Using has_many through

This approach is better because later on in your project you can add more fields and validations in ProductKit model.As you know, our projects are always dynamic and most of the time(all the time) we end up modifying the flow. So, it isbetter to be prepared and use has_many :through from the beginning.

More on, has_many :through on Rails docs.

Migration

class CreateJoinTableProductKits < ActiveRecord::Migration[6.0]
  def change
    create_table :product_kits do |t|
      t.references :product, null: false, foreign_key: true, index: false
      t.references :kit, null: false, foreign_key: { to_table: :products }, index: false
      t.index [:product_id, :kit_id], unique: true

      t.timestamps
    end
  end
end

Enter fullscreen mode Exit fullscreen mode

Model app/models/product.rb

class Product < ApplicationRecord
  has_many :product_kits, dependent: :destroy
  has_many :kits, through: :product_kits
end

Enter fullscreen mode Exit fullscreen mode

Model app/models/product_kit.rb

class ProductKit < ApplicationRecord
  belongs_to :product
  belongs_to :kit, class_name: 'Product'
end

Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
usman1436 profile image
Usman1436

Wow, really helpful.
Thanks