Railsでクラステーブル継承を行う
.png&w=640&q=75)
この記事では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"
endbooksテーブルは共通のカラム、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
endconcernでBookモデルとの関連付けを定義し、ABook, BBookでincludeします。
module Bookable
extend ActiveSupport::Concern
included do
has_one :book, as: :bookable, touch: true
end
endclass ABook < ApplicationRecord
include Bookable
validates :editor, presence: true
endclass 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
endclass ABook < ApplicationRecord
include Bookable
validates :editor, presence: true
# 追加
def publisher
"出版社A"
end
endclass BBook < ApplicationRecord
include Bookable
validates :description, presence: true
# 追加
def publisher
"出版社B"
end
endbook.publisher
=> "出版社A"メソッド委譲により、同じメソッドでも委譲先によって振る舞いを変えることができます。
参考

.png&w=256&q=75)
.png&w=256&q=75)

.png&w=256&q=75)
.png&w=256&q=75)
.png&w=256&q=75)
