STI (単一テーブル継承)とポリモーフィズム関連を✅

絶賛ポートフォリオ悪戦苦闘中なわけで。

その中で、診断内容とお酒テーブルを結びつけたいと思っている。

そしてお酒テーブルから派生して同じカラムを持つ酒の種類テーブル?みたいなものを作りたい。

はいはい、中間テーブルにid持たせて多対多を作るやつね〜と思ったが、

どうやらSTIで関連付る方法があるそうで。

STIってなんですか状態だったので、学習していこうと思う。

初めの構想

enumを使ってこんな感じにすればお酒テーブルから派生して同じカラムを持つ酒の種類テーブルを作れる!と考えた。

class Alcohol < ApplicationRecord
  enum alcohol_types: { beer: 0, shotyu: 1, nihonshu: 2, wine: 3, tyuhai: 4, wiskey: 5 }

しかしこうすると、それぞれのお酒にカラムを持たせることが出来ないと気づいた。

理想は下記のようにしたかった。

Alcoholモデル
#カラム名:型
name:string
description:text
alcohol_percentage:integer
alcohol_amount:integer
pure_alcohol_intake:integer
Beerモデル < Alcoholモデル
#カラム名:型
title:string
description:text
alcohol_percentage:integer
alcohol_amount:integer
pure_alcohol_intake:integer

上記のBeerモデルのように、Alcoholモデルの内容を継承したモデル(Beer,Wine,Shotyuなど)をたくさん作って酒ケジュールとして提供できる書き方を作りたかった。enumだと直感的にそれができなそう。そんな時に見つけたのが、**単一テーブル関連付け(STI)**だった。

STIとは

同じカラム設計のテーブルを、一つのテーブルにまとめて、継承することで余計なテーブルを増やさず、DRYなテーブル設計にするというもの。結びつけられたテーブル達は擬似テーブルであり、DBには実在しないテーブルになる。

お酒テーブルとビールやハイボールなどのテーブルを結びつけたい時。

通常だと、酒テーブルの内容を継承したハイボールテーブル、ビールモデル、サワーモデル、、と複数のテーブルを作る必要がある。

一方で単一テーブル継承という方法を使うと、「必要なテーブルは一つだけ」という状況を作り出すことができる。

どういうことか

例えばビールモデルを作るとすると、こうすることができる。

class Alcohol < ApplicationRecord
  belongs_to :alcoholable, polymorphic: true
end

# インターフェースを明確化するために、moduleで固める
module Alcoholable
  extend ActiveSupport::Concern

  included do
    has_many :alcohols, as: :alcoholable
  end

  def name
    # オーバーライドされなかった場合はエラーが上がるようにしておく
    raise NotImplementedError
  end

  def desctiption
    raise NotImplementedError
  end
  def alcohol_percentage
    raise NotImplementedError
  end
  def alcohol_amount
    raise NotImplementedError
  end
  def pure_alcohol_intake
    raise NotImplementedError
  end
end

class Beer < ApplicationRecord
# Alcoholを使うポリモーフィックなモデルはAlcoholableをincludeする
include Alcoholable

# moduleで定義されているメソッドをオーバーライドする

 def name
    return "beer"
    raise NotImplementedError
  end

  def desctiption
			return "とりあえずビール"
    raise NotImplementedError
  end

  def alcohol_percentage
			return 5
    raise NotImplementedError
  end

  def alcohol_amount
			return 350
    raise NotImplementedError
  end

  def pure_alcohol_intake
			return 14
    raise NotImplementedError
  end
end

実在するテーブルは酒のみ。そのほかは擬似テーブルとなる。Beerモデルは、Alcoholableモジュールをincludeすることで、振る舞いを継承することが出来ている。

こうすることで、複数の同じ振る舞いをするモデルを簡単に作ることができる。

ポリモーフィック関連とどう違うの?

STIは継承の実装で、ポリモーフィック関連は関連の実装なので、前者の対象がテーブルに対して後者はモデルという違いがある

ポリモーフィック関連の特徴

ポリモーフィック関連付けを使うと、ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現できます。

一つのモデルを同じインターフェースを持ったものが扱う(ダックタイピングする)時に便利。

例えば下記のようにPictureモデルと関連づけたいテーブルが複数ある場合、Pictureモデルにableとつくカラムを用意し、関連づけたいテーブルはableがついたカラムをhas_manyすれば簡単に関連づけることができる。

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

ポリモーフィック関連を使って通知機能を実装(enum、既読管理なども) - Qiita

【Rails】単一テーブル継承(STI)について - Qiita

enumチュートリアル

みんなRailsのSTIを誤解してないか!? - Qiita

enumチュートリアル

ActiveRecordのenumで気をつけたい3つのポイント - 弥生開発者ブログ

https://qiita.com/QUANON/items/e8149f4d528966e1b671