下記のようなモデルを実装するとする。
上記は、bookモデルとreportモデルはそれぞれcommentモデルと関連があり(本と報告書に対してコメントする機能)、commentは共通モデルで対応することを想定している。
そんな時に使えるのが、ポリモーフィック関連付け。
ポリモーフィック関連づけ
ポリモーフィック関連付けを使うと、次のことができるようになる。
ある1つのモデルが、他の複数のモデルに属していることを1つの関連付けで表現する
上で検討したER図では、bookモデルとreportモデルは共通のcommentモデルで関連を持ちたいので、ポリモーフィック関連付けの出番ということになる。
commentモデルをbookモデルとreportモデルの両方に従属させたい時には、モデルを次のように宣言する。
class Comment < ApplicationRecord belongs_to :imageable, polymorphic: true end class Book < ApplicationRecord has_many :comments, as: :imageable end class Report < ApplicationRecord has_many :comments, as: :imageable end
belongs_to
をポリモーフィックで宣言すると、任意のモデルから利用可能なインタフェースとして設定したとみなせる。
これのおかげで、@book.comments
や @report.comments
といった形で、各モデルのインスタンスから、コメントを参照できるようになる。
ポリモーフィックなモデルを生成する
上で示したモデルを作ってみる。下記コマンドでマイグレーションファイルのテンプレートを作成する。
% rails g migration CreateComments
次にマイグレーションファイルに、属性、ポリモーフィックと外部キーを追加する。
class CreateComments < ActiveRecord::Migration[6.1] def change create_table :comments do |t| t.text :content t.references :imageable, polymorphic: true t.belongs_to :user, foreign_key: true t.timestamps end end end
マイグレーションファイルを作ったら、モデルを生成する。
% rails db:migrate
次に、コントローラの設定をしていく。
# app/models/comment.rb class Comment < ApplicationRecord ... belongs_to :user # 外部キー用 belongs_to :imageable, polymorphic: true # ポリモーフィック用 ... end # app/models/book.rb class Book < ApplicationRecord ... has_many :comments, as: :imageable # ポリモーフィック用 ... end # app/models/report.rb class Report < ApplicationRecord ... has_many :comments, as: :imageable # ポリモーフィック用 ... end # app/models/user.rb class User < ApplicationRecord ... has_many :comments, dependent: :destroy # ユーザ消えたらコメントも削除 ... end
試しに操作してみる。
% rails c irb(main):001:0> book = Book.first (1.0ms) SELECT sqlite_version(*) Book Load (0.1ms) SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ? [["LIMIT", 1]] irb(main):002:0> book.comments.create(id:1,content:"test",user_id: 1) TRANSACTION (0.1ms) begin transaction User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] Comment Create (1.3ms) INSERT INTO "comments" ("id", "content", "imageable_type", "imageable_id", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?) [["id", 1], ["content", "test"], ["imageable_type", "Book"], ["imageable_id", 1], ["user_id", 1], ["created_at", "2022-09-08 14:13:47.757347"], ["updated_at", "2022-09-08 14:13:47.757347"]] TRANSACTION (0.5ms) commit transaction
作ったデータを確認してみる。
% rails db sqlite> select * from comments; 1|test|Book|1|1|2022-09-08 14:13:47.757347|2022-09-08 14:13:47.757347