😎
【Rails】一つのモデルに対して複数の意味を持たせたい(class_name, inverse_of )
実現させたいこと
例えばユーザー(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.name
と book.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
参考
Discussion