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_many :through, but your mileage may vary!