クラスの継承（単一テーブル継承）について【虎の巻】

### 【概要】
---
作業時に、schemaの情報が定義されていないのにも関わらず、ActiveRecordでテーブルの情報を使用できることに疑問を感じ、調査してみると、単一テーブル継承と呼ばれる考え方が記されておりました。
下記に考え方や手順を備忘録として、残そうと思います。

### 環境
---
この記事は下記の実行環境で動作確認しております。
・Rails 7.0.4 
・Ruby 3.1.4
### 【クラスの継承について】
---
まず、警鐘について確認したいと思います。

継承とはあるクラスを継承し、新たにクラスを作成することを指します。
┗例 Animalクラスを継承したdogクラスなど。(下記参考)

恩恵として、継承元の親クラス(スーパークラス)で定義された機能を子クラス(サブクラス)でも使用ができるようになります。

![スクリーンショット 2023-08-27 20.55.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538926/b87c2adb-6d5f-c933-7f2f-5301846d1874.png)
---
### 【単一テーブル継承(STI)とは】
---
上記のような継承関係をRailsなどで扱う場合、各テーブルでカラム名が重複してしまいます。
上記の例で言うと、cryが重複していますね。
このような場合、役立つのが単一テーブル(以降STIと省略)になります。
STIを使用するとDBのテーブル1つに集約して定義することが可能になり、1つのテーブルでデータをまとめることができます！
これだけだとイメージがつかないと思いますので、少しずつ紐解いて考えていきましょう！

### 実装する前のイメージ
---
サンプルを1つ出して考えてみましょう。
ここで定義するテーブルのイメージを以下とします。

[![Image from Gyazo](https://i.gyazo.com/59f685b1995885e41d5c8380c88b7b6e.png)](https://gyazo.com/59f685b1995885e41d5c8380c88b7b6e)

ポイントは「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