Rails/InverseOfは何の警告か?
アソシエーションを定義する際に明示的に :inverse_of を指定すべき箇所で指定されていないことを警告している。
:inverse_ofとは何者か?
Railsガイドより。
ActiveRecordで、双方向の関連付けを明示的に宣言するもの。
class User
has_many :posts
end
class Post
belongs_to :user
end
inverse_ofが効いていない場合、下例のようになる。
user = User.first
post = user.posts.first
user.name == post.user.name #=> true
user.name = "hogehoge"
user.name == post.user.name #=> false
別々のオブジェクトとして扱われるため、postをたどってuserのnameを変更できない。
inverse_ofが効いている場合、双方向に関連付けが行われて同じオブジェクトになる。
今まで書いた記憶がない。。。
Rails4.1以降、Railsが自動でつけてくれるようになった。
それ以前は都度SQLを発行してインスタンスを生成していた。
そのため、以下のようなことが起こり得た。
・ 同じレコードを参照しているはずなのに、データを取得するためにSQLが発行される
・ 更新したレコードの内容が、アソシエーション経由だと反映されない
しかし、自動で付けてくれるならなぜ警告されたんだ。
:inverse_ofを明示的に指定すべきパターンとは?
foreign_keyやscopeが指定されている場合など、inverse_ofを明示的に指定しないとこのオプションが効かないパターンがある。
foreign_key
次のように、foreign_keyを指定して関連付けされている場合、inverse_ofが自動で適用されないため明示する必要がある。
class User < ApplicationRecord
has_many :posts, foreign_key: :id
end
class Post < ApplicationRecord
belongs_to :user
end
user = User.first
post = user.posts.first
# user.name と post.user.name は同じオブジェクトを見ていて欲しい
user.name = "変更" # user.nameを変更
user.name == post.user.name #=> false
scope
次のように、scopeを含む場合は自動でinverse_ofが適用されないため、明示する必要がある。
class User < ApplicationRecord
has_many :posts, -> () { order(:created_at) }
end
class Post < ApplicationRecord
belongs_to :user
end
user = User.first
post = user.posts.first
# user.name と post.user.name は同じオブジェクトを見ていて欲しい
user.name = "変更" # user.nameを変更
user.name == post.user.name #=> false
どう書くか?
has_many - belongs_toの場合は、
belongs_toで紐付けされているモデルを設定すれば良い。
class User < ApplicationRecord
has_many :posts, -> () { order(:created_at) }, inverse_of: :user
end
class Post < ApplicationRecord
belongs_to :user
end