QA@IT

ActiveRecordで2つのmodelに共通のフィールドを作るには?

2859 PV

現在ActiveRecordの2つのmodelに共通するフィールドを持たせたいと考えています。しかしどのようにするのがスマートな実装なのかわかりません。

このQAサービスを例に挙げて説明させていただくと、
「質問」と「回答」という2つのテーブルがあります。
「質問」には「タイトル」が、「回答」には「アクセプトフラグ」というフィールドがあります。
でもこの2つにはどちらも「本文」と「ユーザ名」という共通のフィールドがあります。
なのでこの共通のフィールドをまとめるための何かを作りたい。

という感じです。
ご回答お待ちしております。

回答

主な手法として 2 つ挙げられると思います。

単一テーブル継承

type という列を持つ 1 つのテーブルにデータを保存し、サブクラスで振る舞いを変えるやり方です。

# 質問 & 回答テーブル定義
create_table :entries do |t|
  t.string :type
  t.references :user
  t.text :body
  t.string :title
  t.boolean :accepted
end

# 質問 & 回答モデル
class Entry < ActiveRecord::Base
  belongs_to :user 
  validates_presence_of :body
end

# 質問モデル
class Question < Entry
  validates_presence_of :title
end

# 回答モデル
class Answer < Entry
  validates_inclusion_of :accepted, in: [true, false]
end

mixin

テーブルは別にして、共通する機能を module に切り出し include するやり方です。

# 質問テーブル定義
create_table :questions do |t|
  t.references :user
  t.text :body
  t.string :title
end

# 回答テーブル定義
create_table :answers do |t|
  t.references :user
  t.text :body
  t.boolean :accepted
end

# 質問モデル
class Question < ActiveRecord::Base
  include ActsAsEntry
  validates_presence_of :title
end

# 回答モデル
class Answer < ActiveRecord::Base
  include ActsAsEntry
  validates_inclusion_of :accepted, in: [true, false]
end

# 質問・回答に共通する振る舞い
module ActsAsEntry
  def self.included(receiver)
    receiver.class_eval do
      belongs_to :user 
      validates_presence_of :body
    end
  end
end

単一テーブル継承は便利ですが、データベースでの制約 (NULL 不可など) がモデルごとに設定できないなどの問題があります。

mixin を使った方法だと、データベース上は無関係なテーブルのデータに対し、共通の振る舞いを定義することができます。

データベースの構造をどうしたいかによって選択するのがよいと思います。

編集 履歴 (0)
  • お返事ありがとうございます。
    この様な方法があったのですね。使わせていただきます。
    -
ウォッチ

この質問への回答やコメントをメールでお知らせします。