Let's take the simple example of a blog with:
- A post
habtm
tags - A tag
habtm
posts
Let's assume that a Post
and Tag
model already exist. This is the migration for the join table
class CreatePostsTags < ActiveRecord::Migration[5.1]
def change
create_table :posts_tags, id: false do |t|
t.belongs_to :post, index: true
t.belongs_to :tag, index: true
end
end
end
And the relations in each model
class Post < ApplicationRecord
has_and_belongs_to_many :tags
end
class Tag < ApplicationRecord
has_and_belongs_to_many :posts
end
Cool! it works !
Few months later, let's assume that you want to order tags in each post for searching purposes.
In our case, has_many :through
permits to declare the join table as model. And so, in order to operate (filter, sort, allow, reject, etc..) at a join table level.
That permits to store the rank
of the tag in the post's tag list direclty in the join model.
class CreatePostsTags < ActiveRecord::Migration[5.1]
def change
create_table :posts_tags, id: false do |t|
t.belongs_to :post, index: true
t.belongs_to :tag, index: true
end
end
end
A model needs a primary key to be retrieved. By convention rails uses the id
column as primary key. The problem is that we've removed the id
column from our join table (id: false
). this makes sense because we just need the post_id
and tag_id
to retrieve a record.
But in order to use has_many :through
, we need to add a primary key to the posts_tags
table. So how to do so ?
class AddPrimaryKeyAndRankToPostsTags < ActiveRecord::Migration[5.1]
def change
rename_table 'posts_tags', 'post_tags'
add_column :post_tags, :id, :primary_key
add_column :post_tags, :rank, :integer, default: 0
end
end
Then in models
class PostTag < ApplicationRecord
belongs_to :post
belongs_to :tag
end
class Post < ApplicationRecord
has_many :post_tags, -> { order(rank: :asc) }
has_many :tags, through: :post_tags
end
class Tag < ApplicationRecord
has_many :post_tags
has_many :posts, through: :post_tags
end
Voilà !
this helped me better understand the shift from HABTM to HMT, thank you!