ファーストビュー画像
ヘッダーロゴ
ホームアイコン
>
>
Railsでクラステーブル継承を行う
バックエンド

Railsでクラステーブル継承を行う

作成日2024/05/13
更新日2024/05/23
アイキャッチ
# Ruby on 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"

メソッド委譲により、同じメソッドでも委譲先によって振る舞いを変えることができます。

参考

https://railsguides.jp/association_basics.html

share on
xアイコンfacebookアイコンlineアイコン