has_and_belongs_to_many
and has_many :through
are two choices when implementing a many to many relationships with Active Record. They both requires a join table to work.
has_and_belongs_to_many
automatically expects a join table to be named in a convention (you remember what’s the convention?1). While has_many :through
you can name your join table.
Take famous example from twitter, A following relationship. How do we approach that? Here is what I would suggest:
class FollowingRelationship < ApplicationRecord
belongs_to :follower,
class_name: "User",
counter_cache: :followed_users_count
belongs_to :followed_user,
class_name: "User",
counter_cache: :followers_count
end
create_table "following_relationships" do |t|
t.bigint "follower_id", null: false
t.bigint "followed_user_id", null: false
t.index ["followed_user_id"], name: "index_following_relationships_on_followed_user_id"
t.index ["follower_id"], name: "index_following_relationships_on_follower_id"
end
class User < ApplicationRecord
has_many :followed_user_relationships,
foreign_key: :follower_id,
class_name: "FollowingRelationship",
dependent: :destroy
has_many :followed_users,
through: :followed_user_relationships
has_many :follower_relationships,
foreign_key: :followed_user_id,
class_name: "FollowingRelationship",
dependent: :destroy
has_many :followers,
through: :follower_relationships
end
create_table "users" do |t|
t.integer "followed_users_count", default: 0, null: false
t.integer "followers_count", default: 0, null: false
end
add_foreign_key "following_relationships", "users", column: "followed_user_id"
add_foreign_key "following_relationships", "users", column: "follower_id"
It takes additional setup compare to has_and_belongs_to_many
but what happens when the business needs arrive that you need to extend the join table?
Another reasons is good design always encourages use many more individual objects to compose what we want to do. We want to put the logic at where it belongs.
I also seen a lot of people migrating from has_and_belongs_to_many
to has_many :through
, but your mileage may vary!