Ruby on Railsで関連するモデルをまとめる仕組みとして、STIとPolymorphicがあります。 この記事では、それぞれの概要や適するケース、実装方法について解説します。

概要

STI

STI(Single Table Inheritance)は、単一のテーブルを継承する仕組みのことをいいます。 つまり、複数のモデルで1つのテーブルを共有するのがSTIです。

複数のモデルにおいて、構造は同じだがふるまいが異なるというケースに適しています。

たとえば、一般ユーザ(Member)と管理者(Admin)を単一のusersテーブルで管理する、などが考えられます。

Polymorphic

Polymorphicは、あるモデルを複数のモデルに属させる仕組みのことをいいます。

特定のふるまいを複数のモデルに持たせるケースに適しています。

たとえば、コメント(Comment)というふるまいを記事(Article)や掲示板(Board)に持たせる、などが考えられます。

実装方法

STI

STIの実装手順は2ステップで、まず(1)継承元モデルのテーブルにtypeカラムを追加し、あとは必要に応じて(2)継承先モデルを作成します。

まず、Userモデルと、それを永続化するusersテーブルを作成します。 テーブルにはtypeカラムを追加します。

$ bin/rails g model User type:string
$ bin/rake db:migrate

次に、Userモデルを継承するモデルを作成します。 ここでは異なるふるまいを持たせる例として、can?メソッドを定義しています。

class Member < User
  def can?
    false
  end
end

class Admin < User
  def can?
    true
  end
end

コンソールで確認してみます。

member = Member.create
member.class #=> Member
member.type  #=> "Member"
member.can?  #=> false

Polymorphic

Polymorphicの実装手順は、まず(1)ふるまいを定義するモデルにxxx_typexxx_idという2つのカラムを追加します。 次に、(2)そのモデルと、それを属させるモデルに対して関連を定義します。

まず、Comment、Article、Boardモデルを作成します。 commentsテーブルには必要なカラムを追加します。

$ bin/rails g model Comment commentable_resource_type:string commentable_resource_id:integer
$ bin/rails g model Article
$ bin/rails g model Board
$ bin/rake db:migrate

次に、各モデルに対して関連を定義します。

class Comment < ActiveRecord::Base
  belongs_to :commentable_resource, polymorphic: true
end

class Article < ActiveRecord::Base
  has_many :comments, as: :commentable_resource
end

class Board < ActiveRecord::Base
  has_many :comments, as: :commentable_resource
end

カラム名や関連名は任意のものでよいですが、これらは一致させる必要があります。 つまり、カラム名がxxx_typexxx_idの場合、関連はbelongs_to :xxxとなります。

コンソールで確認してみます。

article = Article.create
comment = article.comments.create
comment.commentable_resource.class #=> Article
comment.commentable_resource.id    #=> 1

おわりに

STIとPolymorphicの仕組みや適するケースを理解しておくと、同じようなケースが生じた際にきれいに設計できると思います。 頭の片隅にでもとどめておくとよいかもしれません。