through 中間テーブルを経由した関連

使用可能な関連付け

  • has_many
  • has_one

対となる関連に指定するオプション

なし

概要

中間テーブルを介した多対多の関連を実現する、というのが一般的な説明だが、より端的で実態に即した言い方をするなら、関連を連鎖的に辿るショートカットを用意しているにすぎない。

著者(Author)、書籍(Book)、それらを多対多で結ぶ執筆者(Authorship)を考える。

# Author.column_names
# => ["id", "profile_id", "created_at", "updated_at"]
class Author < ApplicationRecord
  has_many :authorships
  has_many :books, through: :authorships
end

# Book.column_names
# => ["id", "created_at", "updated_at"]
class Book < ApplicationRecord
  has_many :authorships
  has_many :author, through: :authorships
end

# Authorship.column_names
# => ["id", "author_id", "book_id", "created_at", "updated_at"]
class Authorship < ApplicationRecord
  belongs_to :author
  belongs_to :book
end

このようにthroughを使って定義すると、以下のような操作ができるようになる。

Author.create
Book.create
Author.last.books << Book
Author.last.books # Booksの配列

ここにさらにプロフィール(Profile)を追加して、著者(Author)も以下のように変える。

# Profile.column_names
# => ["id", "created_at", "updated_at"]
class Profile < ApplicationRecord
  has_one :author
  has_many :authorships, through: :author
  has_many :books, through: :authorships
end

# Author.column_names
# => ["id", "profile_id", "created_at", "updated_at"]
class Author < ApplicationRecord
  belongs_to :profile
  has_many :authorships
  has_many :books, through: :authorships
end

こうすると以下のようなことができる。

Profile.create
Author.create(profile: Profile.last)
Book.create
Author.last.books << Book.last
Profile.last.books # Authorに関連しているBookの配列が返る

このように、多対多の中間テーブルを介した関連を実現するというより、Profile.last.author.authorships.map(&:books).flattenと書かなくてよくするショートカットであり、発行するSELECT文も必要なものに絞り込んでくれる機能である。

has_onethroughを使う時はどういう時かというと、belongs_toの関連から別の関連を辿る場合である。

# Authorship.column_names
# => ["id", "author_id", "book_id", "created_at", "updated_at"]
class Authorship < ApplicationRecord
  belongs_to :author
  belongs_to :book

  # belongs_toのauthorを経由してprofileまでたどっている
  has_one :profile, through: :author
end