Ruby
Rails

Ruby on Railsでクラステーブル継承・具象テーブル継承を実現する

Ruby on Railsでは単一テーブル継承しかサポートしていないが、クラステーブル継承・具象テーブル継承を実現しようと思ったときにアプリケーション側の実装はどんな感じが良いのか考えてみる。


継承とデータベース設計

まず前提。

アプリケーションの実装でクラス間に継承関係があった場合に、それをデータベース側で表現する方法として、PofEAAに以下3つが示されていて、RoRでは単一テーブル継承のみサポートされている。


  • 単一テーブル継承

http://bliki-ja.github.io/pofeaa/SingleTableInheritance/


  • クラステーブル継承

http://bliki-ja.github.io/pofeaa/ClassTableInheritance/


  • 具象テーブル継承

http://bliki-ja.github.io/pofeaa/ConcreteTableInheritance/


RoRでの実装

テーブル関係をコードに落とそうと思ったとき、共通テーブルを親クラスとして定義して継承する方法と、共通テーブルの特徴をモジュール化してmixinする2つの方法が考えられると思う。両方とも共通の特徴をクラスに取り込むという意味では同じなのだが言語的には振る舞いが異なるのでそのあたりは下記参照。

https://qiita.com/pink_bangbi/items/2c2f17516cd3a7b4eeac


mixin

共通となる特徴をモジュール化して外部に切り出す方法。モジュールとして切り出して各モデルがモジュールをincludeすることで、コード的には関連するモデルが全て直接ActiveRecordを継承することになる。コーディングするときはuser.subscriptions的に利用できる。

結論から言うと後述の継承を用いた方法はRails的にデメリットが多くて、mixinのほうがおすすめである。

module Chargeable

belongs_to :customer
has_many :subscriptions
...
end

class User < ActiveRecord::Base

include Chargeable
end

class Company < ActiveRecord::Base

include Chargeable
end


継承

RailsのSTIライクに共通属性を継承する形で利用できるようにする方法。他の言語でのオブジェクト指向プログラミングに慣れている場合は見た目的にもわかりやすくてしっくりくるかなと思う。

ただ、Rails的に結構トラップがあると思っているので注意も必要だったりする。

たとえば親クラスが外部に持つアソシエーションを子クラスで呼び出した場合に、子クラスの外部キーが用いられるたりする。(user.subscriptionsした場合、customer.idを外部キーとしてsubscriptionを取得してほしいところuser.idに紐付いたsubscriptionsが取得されてしまう)

この場合以下のように、親クラスにdelegateして user.subscriptions した場合も customer.subscriptions が呼ばれるようにするなど対応が必要だと思う。

ほかにもたとえば、UserCompanyにポリモーフィック関連するモデルが存在した場合、hogerable_typeカラムに親クラスが設定されてしまうなど。

class Customer < ApplicationRecord

has_many :subscriptions
has_one :user
has_one :company

end

class User < Customer

belongs_to :customer
has_many :hoges, as: :hogerable
delegate :subscriptions, to: :customer
end

class Company < Customer

belongs_to :customer
has_many :hoges, as: :hogerable
delegate :subscriptions, to: :customer
end

class Hoge < ActiveRecord::Base

belongs_to :hogerable, polymorphic: true
end


結論