Railsでクラステーブル継承を行う
この記事ではRailsのActiveRecord::DelegatedTypeについて説明します。
Delegated Typeとは
共通する項目と、それぞれ固有の項目がある別々のデータをまとめて扱いたい場合があります。
例えば本の販売サイトがあり、出版社A, 出版社Bの2社から本を仕入れているとします。
それぞれの出版社から仕入れる本はタイトル・値段・著者という共通の情報と、A社にしかない編集者、B社にしかない説明という情報が存在します。
仕入れた本を1つのbooksテーブルに保存していくと必ずnullの箇所が存在してしまします。
また、編集者、説明はそれぞれの出版社で必須としたくてもnot null制約をかけることができません。
Delegated Typeでは共通の項目を管理するテーブルを作成し、固有の項目はそれぞれの別テーブルで管理をします。
このように別テーブルに分けることで個々のテーブルに必要になった項目はそれぞれのテーブルに追加すればよく、not null制約も使うことができます。
Delegated Typeを使ってみる
次のようなスキーマを準備します。
create_table "a_books", force: :cascade do |t|
t.string "editor", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "b_books", force: :cascade do |t|
t.string "description", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "books", force: :cascade do |t|
t.integer "bookable_id", null: false
t.string "bookable_type", null: false
t.string "title", null: false
t.integer "price", null: false
t.string "author"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["bookable_id"], name: "index_books_on_bookable_id"
end
books
テーブルは共通のカラム、bookable_type
, bookable_id
カラムを追加します。bookable_type
には対応しているクラス名前が、 bookable_id
にはそのクラスのidが保存されます。 a_books
, b_books
にはそれぞれ固有のカラムを持たせています。
モデルの作成
booksモデルにdelegeted typeを使うことを宣言します。
class Book < ApplicationRecord
delegated_type :bookable, types: ["ABook", "BBook"]
validates :title, :price, :author, presence: true
end
concernでBookモデルとの関連付けを定義し、ABook, BBookでincludeします。
module Bookable
extend ActiveSupport::Concern
included do
has_one :book, as: :bookable, touch: true
end
end
class ABook < ApplicationRecord
include Bookable
validates :editor, presence: true
end
class BBook < ApplicationRecord
include Bookable
validates :description, presence: true
end
レコードの作成
レコードの作成は共通のモデルと固有のモデルを同時に作成します。
Book.create!(title: "タイトル01", price: 1200, author: "タイトル01", bookable: ABook.new(editor: "タイトル01"))
作成されたオブジェクトのentryable_typeにはABook
が保存されています。
book = Book.last
book.bookable_type
=> "ABook"
オブジェクトのtypeが"ABook"なのか"BBook"なのか確認するためのメソッドbook.a_book?
, book.b_book?
も使えるようになっています。
book.a_book?
=> true
book.b_book?
=> false
その他のメソッドはこちらから確認できます。
メソッドを委譲する
Bookモデルで作成したメソッドをABookモデル、BBookモデルに委譲することができます。
今回は出版元を返すpublisher
メソッドを追加します。
class Book < ApplicationRecord
delegated_type :bookable, types: ["ABook", "BBook"]
delegate :publisher, to: :bookable # 追加
validates :title, :price, :author, presence: true
end
class ABook < ApplicationRecord
include Bookable
validates :editor, presence: true
# 追加
def publisher
"出版社A"
end
end
class BBook < ApplicationRecord
include Bookable
validates :description, presence: true
# 追加
def publisher
"出版社B"
end
end
book.publisher
=> "出版社A"
メソッド委譲により、同じメソッドでも委譲先によって振る舞いを変えることができます。
参考