😎

【Rails】一つのモデルに対して複数の意味を持たせたい(class_name, inverse_of )

2024/07/08に公開

実現させたいこと

例えばユーザー(User)と本(Book)のモデルが存在している時
Userに二つの意味を持たせたいことがある

  • 本を所有しているユーザー(User)
  • 本の著者(Author)
book.author
# => 本の著者を取得
book.user
# => 本の所有者を取得

同じUserモデルを使って、author、userをそれぞれ取得できる

モデル作成

マイグレーションファイルは以下のようにBookに外部キーとして

  • user_id
  • author_id

を持たせます

class CreateBooks < ActiveRecord::Migration[7.1]
  def change
    create_table :books do |t|
      t.belongs_to :user, null: false
      t.belongs_to :author, null: false

      t.timestamps
    end
  end
end

モデルは下記のようにclass_nameオプションを指定。

# ユーザーモデル
class User < ApplicationRecord
  has_many :books, inverse_of: :author
end

# 本モデル
class Book < ApplicationRecord
  # 本の所有者
  belongs_to :user
  # 本の著者
  belongs_to :author, foreign_key: "author_id", class_name: "User", inverse_of: :books
end

上記を設定することで、
book.userで本の所有者を取得できるのはもちろん、
book.authorで本の著者を取得できるようになる。

inverse_ofについて

この時登場するのが双方向関連付けを宣言するための inverse_ofです。

railsでは元々何もしなくても双方向関連付けが設定されていますが、
今回のようにUserモデルに別の意味を持たせるよう変更を加えた場合、別途指定が必要になります。

双方向関連付け確認方法

双方向関連付けとは・・・
二つのモデル間でお互いのデータを参照したり、更新したりするとき、データの齟齬が起こらない。
別のテーブル経由で値を参照しても同じ結果が得られる。
(例えば user.name == book.author.name)
(例えば book.title == user.books.first.title)

双方向関連付けを行なっていないと、
更新したはずのUserの情報が更新されていないみたいなことが起こります。

👇関連付けできていない例

user = User.first
book = user.books.first

user.name
# => "ユーザー1"
book.author.name
# => "ユーザー1"

user.name = "ユーザー2" # userオブジェクトのnameを変更

user.name
# => "ユーザー2"
book.author.name 
# => "ユーザー1" (←ユーザー2の出力されるはず????)

原因としては、user.namebook.author.name で取得できるオブジェクトは一見同じように見えるが、
実は異なるオブジェクト

user.object_id
# => 11111
book.author.object_id
# => 12121

実はオブジェクトidが異なる

👇関連付け成功例

user = User.first
book = user.books.first

# オブジェクトidが一致している
user.object_id # => 1234
book.object_id # => 1234

user.name = 'ユーザー1'

user.name
# => 'ユーザー1'
book.author.name
# => 'ユーザー1'

# user.name と book.author.name が一致していればOK

参考

https://railsguides.jp/association_basics.html#双方向関連付け

https://zenn.dev/igaiga/books/rails-practice-note/viewer/ar_inverse_of

Discussion