Railsで単一テーブル継承(Single Table Inheritance)
categories: Ruby Rails tags: HowTo Programming Rails Ruby
ActiveRecord以外のORマッパーはどうなのかよく知らないのですが、Rails(というかActiveRecord)では、DB上の一つのテーブルを複数のモデルで共有する「単一テーブル継承(Single Table Inheritance)」というものが存在します。今日はこの単一テーブル継承についてちょっと紹介します。
ここでは種々のメッセージ機能を単一テーブル継承で実現する方法を例に、単一テーブル継承について紹介します。今回想定するメッセージ機能はSNSなんかでよく使われるであろう以下の3つです。
- 一般メッセージ:NormalMessage
- 招待メッセージ:InvitationMessage
- お問い合わせ:InquiryMessage
それでは、すべてのモデルのデータ保存先となるMessageテーブルの定義から始めましょう。
まず単一テーブル継承をActiveRecordで使う為には、DBにtypeというcolumnを用意してやります(これ必須!!)。Messageテーブルの定義はこんな感じです。
create_table :messages do |t| # 全モデル共通で使うカラム t.column :title, :string t.column :body, :text t.column :created_at, :datetime t.column :updated_at, :datetime t.column :type, :string # 必須 # モデルによって使ったり使わなかったりするカラム t.column :from_user_id, :integer t.column :to_user_id, :integer t.column :to_user_address, :string t.column :from_user_address, :string t.column :key, :string t.column :finished, :boolean, :default => false end
ここでtypeというカラムは、モデルのクラス名を保存する場所であり、こいつがActiveRecordでSingle Table Inheritanceを使うときの肝になります。それ以外は全モデルで使うカラム、モデルによって使ったり使わなかったりするカラムを、すべてMessageテーブルに定義します。
次にModel側の実装に移ります。まず、すべてのメッセージモデルの元となるMessageクラスを作ります。
class Message < ActiveRecord::Base belongs_to :from, :class_name => 'User', :foreign_key => :from_user_id belongs_to :to, :class_name => 'User', :foreign_key => :to_user_id def send_mail end def after_create send_mail end validates_presence_of :body end
MessageクラスはActiveRecord::Baseを継承しているので、DBのmessageテーブルと対応づけられます。
ここではすべてのメッセージが、DBに保存された後で、各モデルでオーバーライドされたsend_mailメソッドによってメール送信されるように設計されています。こうすると@message.saveとすれば自動でメール送信までされるので、コントローラ側の実装が楽ちん♪
またMessageクラスで定義されたassociationおよびvalidationは、それ以降のすべてのモデルで有効になります。
次に一般メッセージで利用するNormalMessageクラスを作ります。
class NormalMessage < Message def send_mail ApplicationMailer.send_normal_message self end validates_presence_of :title, :to_user_id, :from_user_id end
ここで重要なのは、NormalMessageクラスはActiveRecord::BaseではなくMessageクラスを継承しているということです。これにより、NormalMessageクラスのデータはmessageテーブルにtype='NormalMessage'として保存されます。
また一般メッセージは、Messageクラスへの追加機能として、titleとto_user_id、from_user_idを必須にし、ApplicationMailerクラスのsend_normal_messageメソッドでメールを送信するように定義されています。
次に招待メッセージで利用するInvitationMessageクラスを作ります。
class InvitationMessage < Message def send_mail ApplicationMailer.send_invitation_message self end def before_create create_key self # ここで何かしらkeyを作る。ここでは書かない。 end validates_presence_of :from_user_id, :to_user_address, :key end
InvitationMessageもMessageを継承しています。また招待キーを発行するため、before_createの中でkeyを作る何らかの処理をします。InvitationMessageをメール送信するのは、ApplicationMailerクラスのsend_invitation_messageメソッドになっています。
あとはお問い合わせですね。
class InquiryMessage < Message def send_mail ApplicationMailer.send_inquiry_message self end validates_presence_of :from_user_address end
もう説明はいらないですよね。
単一テーブル継承を使うのに必要なことはこれだけです。あとは
Message.find :all
で
SELECT * FROM MESSAGE;
のようなSQLが実行され、
NormalMessage.find :all
では
SELECT * FROM MESSAGE WHERE type='NormalMessage';
のようなSQLが実行されます。
コントローラ側で
Message.find :all
としておいて、ビューでは
<%= render :partial => @message.class.name.downcase %<
とすれば、
- _message.rhtml
- _normalmessage.rhtml
- _invitationmessage.rhtml
- _inquirymessage.rhtml
というテンプレートを用意するだけで、各種メッセージの表示もできちゃいます。
注)Railsでは@object.class.nameでクラス名を取得できます。
単一テーブル継承をうまく使えば、モデルだけじゃなくてコントローラやビューまで見やすく整理されます。すばらしい♪
この機能、メッセージングだけじゃなくて、一般会員/有料会員/管理者というような複数種類のユーザを管理するときなんかにも使えますね。
ただ一つ注意しないといけないことは、テーブルが一つに集中するので、DB分散とか考えだすとちょっとややこしくなるという。。。
Single Table Inheritanceの恩恵は、このブログが参考になる?
I.T.System | ありえない仕様変更
ちなみにPHPでもできるらしい。
CakePHPのおいしい食べ方: Single Table Inheritance を CakePHP で実現するには