単一テーブル継承
あるクラスから派生する全てのサブクラスを交通の1つのテーブルに対応させます。DBにある1つのテーブルを、複数のモデルで共有利用することができます。
テーブル設計の概要
記事テーブルにタグ、カテゴリー、著者テーブルを関連付する。
このTaxnomiesテーブルを使ってSTIという継承関係を実現する。
db/schema.rb
create_table "taxonomies", force: :cascade do |t| t.string "type" t.string "name" t.string "slug" t.text "description" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["slug"], name: "index_taxonomies_on_slug" t.index ["type"], name: "index_taxonomies_on_type" end
schema.rbでtaxonomiesモデルが作成されています。しかし、tag、category、authorのモデルテーブルはありません。
それもそのはず、単一テーブル継承においてTaxonomieはスーパークラスで、DB上に実在するテーブルです。
Category、Tag、AuthorクラスはTaxonomyクラスを継承したサブクラスだが、categories、tags、authorsテーブルというものはDB上に存在しない。しかし、STIを使用することで、開発者はCategories、Tags、Authorsテーブルというテーブルが存在するかのようにモデルとDBを扱えるようになるのです。
taxonomy.rb
class Taxonomy < ApplicationRecord validates :name, presence: true, uniqueness: { scope: :type }, length: { maximum: 16 } validates :slug, presence: true, uniqueness: { scope: :type }, length: { maximum: 64 }, slug_format: true end
category.rb
class Category < Taxonomy has_many :articles end
tag.rb
class Tag < Taxonomy has_many :article_tags has_many :articles, through: :article_tags end
author.rb
class Author < Taxonomy has_many :articles has_one_attached :avatar validates :avatar, attachment: { purge: true, content_type: %r{\Aimage/(png|jpeg)\Z}, maximum: 10_485_760 } end
クエリは以下のようになります。
[61] pry(main)> Author.all Author Load (1.1ms) SELECT "taxonomies".* FROM "taxonomies" WHERE "taxonomies"."type" IN ('Author')
[62] pry(main)> Category.all Category Load (0.3ms) SELECT "taxonomies".* FROM "taxonomies" WHERE "taxonomies"."type" IN ('Category')
[63] pry(main)> Tag.all Tag Load (0.3ms) SELECT "taxonomies".* FROM "taxonomies" WHERE "taxonomies"."type" IN ('Tag')
注意点
継承元のテーブルにはtypeカラムを実装しておく必要がある。 どういうことかと言うと、DB上にはTaxonomyテーブルしか存在しないので、category、tag、authorのデータはtaxonomiesテーブルの中に全部ごっちゃで入っていて、どのクラスのデータかを識別するためにtypeカラムを使うって感じ。 そこで、typeカラムというRailsが標準で用意しているカラムを使用すれば、ActiveRecordがいい感じにtypeにサブクラス名を入れてくれるのです。