ひよっこエンジニアの雑多な日記

なんとかWeb系のエンジニアをやっています。

【Rails】多対多のアソシエーションに別名をつけたいあなたに

最近涼しくなってきましたねー…と思ったら次の日は暑かったりで体を壊しそうになります。(2週間前にすでに壊した)

今日は最近コードをレビューする機会があって、多対多のアソシエーションをうまいこと設定できていなくて指摘したかったものの、どうやって設定するか微妙に調べることになったのでもう忘れないようにブログにしておこうと思った次第。

想定

会員がブログを投稿できる機能があって、他の人が書いたブログをお気に入りできるみたいな機能を作る想定で
以下のような感じのテーブル構成をイメージ

f:id:kimuraysp:20170905230916p:plain

とりあえずわかりやすいところから

各モデルのアソシエーションのとりあえずわかりやすいところから記載

# user.rb
class User < ApplicationRecord
  has_many :blogs
  has_many :favorites
end

# blog.rb
class Blog < ApplicationRecord
  belongs_to :user
  has_many :favorites
  has_many :users, through: :favorites
end

# favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :blog
end

多対多の関連がある時はthroughオプションをつけてあげると中間テーブルを経由して関連先のモデルを取得できるようになります。
throughをつけないと中間テーブルの先のレコードを取得するのが一手間必要になり微妙なので設定するのが吉です。

# blogからuserを取得する例

# through設定した場合
blog.users

# through設定しない場合
 blog.favorites.map { |favorite| favorite.user } 

設定しない場合の取得方法はあくまで例ですが何かしら下処理が必要になってしまいます…

で、それならuserにもthroughを設定してblogを取得できるようにしたらいーじゃん!ってなりますよね。
ただ今回はblogとuserにはすでにアソシエーションが存在します。
こんな時は別名をつけてあげれば良いのです!

多対多で中間テーブルの先のモデルとアソシエーションする

こんな感じで実現できます。

# user.rb
class User < ApplicationRecord
  has_many :blogs
  has_many :favorites
  has_many :favorite_blogs, through: :favorites, source: :blog
end

sourceオプションを設定することでuser.favorite_blogsでuserがお気に入りしたブログを一気に取得することができます。

Railsは外部キーの名前などでアソシエーションの判断をしていますが、アソシエーションの名前を外部キーの名前から外れた名前にする場合はそれをオプションをつけて教えてあげる必要があります。
sourceオプションがthroughする際にアクセスすべきモデルを教えるオプションとなります。
source: :blogとすることでfavorite.rbで設定しているbelongs_to :blogを参照するようになり、favorite_blogsというblog_idに紐づくという判断ができない名前であってもblog.rbに辿り着けるようになるといった感じです!(説明わかりづらい説)

なのでこのようなアソシエーションを作成したいとなった場合はsourceオプションを活用しましょう!

ちなみに1対多で違った名前を付ける際の対処

たとえば何らかの理由でhas_many :blogshas_many :write_blogsにしなければいけなくなった際は

class User
  has_many :write_blogs, class_name: 'Blog', foreign_key: :user_id
  # 以下省略
end

みたいな感じで、このアソシエーションはどこのモデルとのアソシエーションなのか、どの外部キーを用いるのかをオプションで明示する必要があります。

最後に

NEW GAMEを見てるとみんな才能があって素敵に思う。
ねねっちを弊社にスカウトしたいのだけどどこで出会えるのかしら