【概要】
作業時に、schemaの情報が定義されていないのにも関わらず、ActiveRecordでテーブルの情報を使用できることに疑問を感じ、調査してみると、単一テーブル継承と呼ばれる考え方が記されておりました。
下記に考え方や手順を備忘録として、残そうと思います。
環境
この記事は下記の実行環境で動作確認しております。
・Rails 7.0.4
・Ruby 3.1.4
【クラスの継承について】
まず、警鐘について確認したいと思います。
継承とはあるクラスを継承し、新たにクラスを作成することを指します。
┗例 Animalクラスを継承したdogクラスなど。(下記参考)
恩恵として、継承元の親クラス(スーパークラス)で定義された機能を子クラス(サブクラス)でも使用ができるようになります。
【単一テーブル継承(STI)とは】
上記のような継承関係をRailsなどで扱う場合、各テーブルでカラム名が重複してしまいます。
上記の例で言うと、cryが重複していますね。
このような場合、役立つのが単一テーブル(以降STIと省略)になります。
STIを使用するとDBのテーブル1つに集約して定義することが可能になり、1つのテーブルでデータをまとめることができます!
これだけだとイメージがつかないと思いますので、少しずつ紐解いて考えていきましょう!
実装する前のイメージ
サンプルを1つ出して考えてみましょう。
ここで定義するテーブルのイメージを以下とします。
ポイントは「taxonomies」テーブルです!
このテーブルでは、継承先「categories」「tags」「authors」を、継承元「taxonomies」で値を一括管理するということですね。これだけみると、普通にテーブルを作成するのと同じように見えますね。
では、なにが違うのでしょうか?実は下記の違いだけで実装ができてしまいます。
・データは全て「Taxonomies」で管理されるということ。
・そのほかのテーブルは実在はしないということ。
ここが通常のテーブル定義と異なるポイントとなります。
また、テーブル設計する際に、「tags」テーブルにしか持たない情報が欲しい場合、事前に継承元の「taxonomies」に定義するようにしましょう。
STIで実装する
上記までのイメージが掴めたら、継承元となる「Taxonomies」テーブルを作成し、DBの方に反映する作業を行いましょう。
下記がオペレーションの手順になります。
◇Taxonomiesテーブルを作成◇
rails generate migration CreateTaxonomies
◇内容を確認◇
<!-- xxxxxxxxxxxxxx_create_taxonomies.rb -->
class CreateTaxonomies < ActiveRecord::Migration[6.1]
def change
create_table :taxonomies, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" 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
end
add_index :taxonomies, :slug, unique: true
add_index :taxonomies, :type
end
end
◇マイグイレーションする◇
rails db:migrate
◇db/shema.rb◇
create_table "taxonomies", charset: "utf8mb4", force: :cascade do |t|
#force: :cascade ->親テーブルのレコードが削除された際に関連する子レコードも自動的に削除するオプション
t.string "type"
t.string "name"
t.string "slug"
t.text "description"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["slug"], name: "index_taxonomies_on_slug"
t.index ["type"], name: "index_taxonomies_on_type"
end
◇モデルに定義◇
<!-- app/models/tag.rb -->
class Tag < Taxonomy #継承関係の記述。
has_many :article_tags
has_many :articles, through: :article_tags
end
<!-- app/models/category.rb -->
class Category < Taxonomy #継承関係の記述。
has_many :articles
end
<!-- app/models/author.rb -->
class Author < Taxonomy #継承関係の記述。
has_many :articles
end
以上が実装となります。実際にテーブルの継承関係が問題ないか確認していきましょう!!
実際の挙動を確認
コンソール上から挙動を確認することができますので、確かめてみましょう。
rails console #コンソール起動
[1] pry(main)> Tag.first
Tag Load (2.6ms) SELECT `taxonomies`.* FROM `taxonomies` WHERE `taxonomies`.`type` = 'Tag' ORDER BY `taxonomies`.`id` ASC LIMIT 1
=> #<Tag:0x0000ffffb55824c8
id: 1,
type: "Tag",
name: "a",
slug: "a",
description: nil,
created_at: Thu, 17 Aug 2023 00:03:36.000000000 JST +09:00,
updated_at: Thu, 17 Aug 2023 00:03:36.000000000 JST +09:00>
サンプルとして1件抽出してみました。
「Tag.first」と打鍵しているのに対して、SQLでは「taxonomies」から抽出されていますね。
SELECT `taxonomies`.* FROM `taxonomies`
上記のように継承元から継承先の情報を抽出できていれば、成功です!
継承元の「taxonomies」、継承先の「tag」テーブルを実際にあるように取得することができております。
【引用元】
・継承関係(動物)
https://medium-company.com/%E3%83%9D%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%95%E3%82%A3%E3%82%BA%E3%83%A0/
・Funna(ふんな)の技術ブログ
https://ryota21silva.hatenablog.com/entry/2020/06/09/184330
・[Rails] STI(単一テーブル継承)とメタプログラミングでDRY
https://qiita.com/kidach1/items/789c2e7aebbcfbd2583e