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
Model
class Product < ApplicationRecord
has_and_belongs_to_many :kits,
join_table: :product_kits,
class_name: 'Product',
association_foreign_key: 'kit_id'
end
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 is
better 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
Model app/models/product.rb
class Product < ApplicationRecord
has_many :product_kits, dependent: :destroy
has_many :kits, through: :product_kits
end
Model app/models/product_kit.rb
class ProductKit < ApplicationRecord
belongs_to :product
belongs_to :kit, class_name: 'Product'
end