inverse_of カスタマイズされた関連の双方向関連を指定する

使用可能な関連付け

  • belongs_to
  • has_many
  • has_one

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

なし

概要

厳密な規則性を把握していないので厳密なパターンはここでは省略するが、ActiveRecordの関連が双方向関連になっている場合、すでにインスタンスを作っている場合は同じインスタンスが使われるケースがある。

例えば以下のような定義がある場合、

class Profile < ApplicationRecord
  has_one :author
end

class Author < ApplicationRecord
  belongs_to :profile
  has_many :authorships
end

class Authorship < ApplicationRecord
  belongs_to :author
end

以下のように関連を辿ってProfileの関連に至ったとしても同じインスタンスを返すようになっている。

profile = Profile.last
profile.object_id == profile.author.authorships.last.author.profile.object_id
# => true

これを以下のように変える。テーブルはそのまま、AuthorAuthorxに変更している。それに伴い、class_nameforeign_keyの設定も追加している。

class Profile < ApplicationRecord
  # has_one :author
  has_one :author, class_name: 'Authorx'
end

class Authorx < ApplicationRecord
  self.table_name = :authors
  belongs_to :profile
  # has_many :authorships
  has_many :authorships, foreign_key: :author_id
end

class Authorship < ApplicationRecord
  # belongs_to :author
  belongs_to :author, class_name: 'Authorx'
end

このように通常の関連定義から逸脱した状態で、同じように関連を辿ると別のインスタンスを返すようになってしまう。

profile = Profile.last
profile.object_id == profile.author.authorships.last.author.profile.object_id
# => false

このような場合でも、通常の関連定義と同じように同じインスタンスを返すようにさせるのがinverse_ofオプションである。

Authorxauthorships関連にオプションを追加する。

  has_many :authorships, foreign_key: :author_id, inverse_of: :author

こうすると、同じインスタンスを返すようになる。

profile = Profile.last
profile.object_id == profile.author.authorships.last.author.profile.object_id
# => true

どの関連にinverse_ofをつける必要があるか

考えるよりも、rubocop-railsというRuboCopのextension gemにRails/InverseOfというCopが用意されているので、それに任せれば良い。

inverse_ofの無効化

inverse_of: falseを指定することで無効化できる。Rails/InverseOfの説明を読む限りでは、inverse_of: nilでは自動的な双方向関連解決の実行を止められないようである。コードを読んでみないとわからないが、inverse_of: nilを試してみた感じではinverse_of: falseと違いがないように見えるので、内部的な処理が違っていてnilだと解決を試みて失敗しているとかなのかもしれない。