rubocopを実行していたらRails/InverseOfという形で怒られたので、inverse_ofとはなんぞやというところから分かったことをアウトプットしていきたいと思います。
inverse_ofオプションとは
Railsガイドによると、双方向の関連付けに使われ、rails4.1以降では指定せずともrails側が自動でつけてくれるが、自分で定義した関連には明示的にオプションを付ける必要があるとのこと。つけないと意図しない挙動をする可能性があるようです。
双方向の関連付けについて詳しくはこちら
https://railsguides.jp/association_basics.html#%E5%8F%8C%E6%96%B9%E5%90%91%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91
今回扱う関連モデル
簡潔にするため、必要ないオプションは省略しています。また、この後出てくるpryでの実行結果においてもSQLの出力は省略してあります。
has_many :sold_tickets, class_name: 'Ticket', foreign_key: 'seller_id'
belongs_to :seller, class_name: 'User'
inverse_ofオプションなしの場合
コンソールを立ち上げて、ニックネームがさかいのuserを取得します。
pry(main)> user = User.find(1)
=> #<User id: 1, email: "aaa@example.com", nickname: "さかい", created_at: "2020-11-19 21:18:56", updated_at: "2020-12-06 04:29:04">
次にさかいの出品したチケットを1枚取得します。
pry(main)> ticket = user.sold_tickets.first
=> #<Ticket:0x00007fd018ff31e8
ニックネームを酒井に変更します。
pry(main)> user.nickname = "酒井"
=> "酒井"
すると、同じオブジェクトを参照しているはずのticket.seller.nicknameでは値が書き換わっていません。
pry(main)> user.nickname == ticket.seller.nickname
=> false
pry(main)> ticket.seller.nickname
=> "さかい"
これは別々のオブジェクトが生成されているためです。
pry(main)> user.equal? ticket.seller
=> false
このままでは、意図しないエラーが起きてしまう可能性がありそうです。
さあここでinverse_ofオプションの出番です。
inverse_ofオプションありの場合
has_many :sold_tickets, class_name: 'Ticket', foreign_key: 'seller_id', inverse_of : 'seller'
同じようにuserとticketを定義し、ニックネームを酒井に変更します。
pry(main)> user = User.find(1)
=> #<User id: 1, email: "aaa@example.com", nickname: "さかい", created_at: "2020-11-19 21:18:56", updated_at: "2020-12-06 04:29:04">
pry(main)> ticket = user.sold_tickets.first
=> #<Ticket:0x00007fd018ff31e8
pry(main)> user.nickname = "酒井"
=> "酒井"
すると、先程とは違い同一のオブジェクトを参照し、値が書き換わっていることがわかります。つまり、inverse_ofは同一のオブジェクトを参照させるためのオプションだったわけです。
pry(main)> user.nickname == ticket.seller.nickname
=> true
pry(main)> ticket.seller.nickname
=> "酒井"
pry(main)> user.equal? ticket.seller
=> true
ちなみに、Ticketモデルにinverse_ofオプションをつけても同一のオブジェクトを参照しません。
関連定義
has_many :sold_tickets, class_name: 'Ticket', foreign_key: 'seller_id'
belongs_to :seller, class_name: 'User', inverse_of: 'sold_tickets' # 追記
実行結果
pry(main)> user.nickname == ticket.seller.nickname
=> false
pry(main)> user.equal? ticket.seller
=> false
inverse_of 'seller'の「seller」はUserクラスのインスタンスのオブジェクトはsellerメソッドで取得したオブジェクトと同一のものとしますよという意味だったんですね。
以上になります。何かまちがっている箇所等ありましたらご指摘ください。