読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Rails4でポリモフィックのリレーションを実装する

Rails中級 Rails Model

Railsでは、ActiveRecordのhas_manyasオプションとbelogns_topolymorphicオプションを使うことで、DBのポリモフィックのリレーションを簡単に実装することができます。

動作確認

  • Rails 4.1
  • ActiveRecord 4.1

目次

  1. ポリモフィックリレーションとは
  2. ポリモフィックなテーブルの作成
  3. モデルにhas_manyとbelongs_toを追加する
  4. 使えるようになるメソッド
  5. ポリモフィックの画面を作成


1. ポリモフィックリレーションとは

説明のために次のER図を実装してみます。
f:id:nipe880324:20141125015430p:plain:w480


記事(Article)とイベント(Event)にそれぞれコメント(Comment)を複数つけることができます。
記事用のコメントテーブル、イベント用のコメントテーブルとそれぞれ作ると、それぞれ同じような処理を記述しなければいけないため、コメントテーブルは参照先となるテーブルをArticleかEventのどちらかをもつようにします。
このような関係を「ポリモフィック関連」と言います。




2. ポリモフィックなテーブルの作成

まず、Eventモデルとテーブルを作成します。

rails g model event name:string content:text


次に、Articleモデルとテーブルを作成します。

rails g model article title:string body:text


最後に、Commentモデルとテーブルを作成します。
このとき、参照先のIDを格納する外部キーのxxx_idと参照先のモデル名を格納するxxx_typeという2つのカラムが必要になります。

rails g model comment content:text commentable_id:integer commentable_type:string


インデックスを追加しておきます。

# db/migrate/yyyymmddhhMMss_create_comments.rb
class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.text :content
      t.integer :commentable_id
      t.string :commentable_type

      t.timestamps
    end

    add_index :comments, [:commentable_id, :commentable_type]
  end
end


マイグレーションを実行します。

rake db:migrate


3. モデルにhas_manyとbelongs_toを追加する

belogns_topolymorphicオプションをつけて記載します。
デフォルトではbelongs_toの引数のcommentableと上記のxxx_idxxx_typeは同じにする合わせる必要があります。(class_nameやforeign_keyオプションを使うことで変更可能です。)

# app/models/comment.rb
class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end


次に、EventモデルとArticleモデルにhas_manyasオプションをつけて記載します。

# app/models/event.rb
class Event < ActiveRecord::Base
  has_many :comments, as: :commentable
end

# app/models/article.rb
class Article < ActiveRecord::Base
  has_many :comments, as: :commentable
end

これで、ポリモフィックの設定は完了です。




4. 使えるようになるメソッド

ポリモフィック関連を設定するとCommentを作成すると自動的に、下記のようにcommentable_idcommentable_typeに適切な値が設定されます。

# EventのCommentを作成すると自動的にCommentレコードのcommentable_idとcommentable_typeにEventの情報が設定される
event = Event.create name: "event1"
event_comment = event.comments.create content: "This event is awesome!"
event_comment
# => <Comment id: 1, content: "This event is awesome!", commentable_id: 1, commentable_type: "Event", created_at: ...>

# 同様に、ArticleのCommentを作成すると自動的にCommentレコードのcommentable_idとcommentable_typeにArticleの情報が設定される
article = Article.create title: "article1"
article_comment = article.comments.create content: "This article is great!"
article_comment
# => <Comment id: 2, content: "This article is great!", commentable_id: 1, commentable_type: "Article", created_at: ...>


5. ポリモフィックの画面を作成

おまけとして、ポリモフィック関連のCommentsコントローラーの実装方法も紹介します。
次のようなルーティングになっている場合、Commentsコントローラーでは、動的にArticleクラスかEventクラスを取得する必要があります。

# config/routes.rb

resources :articles do
  resources :comments
end

resources :events do
  resources :comments
end


load_commentableメソッド内で、リクエストのURLからArticleかEventを判定し、ポリモフィック関連に合わせて動的にモデルを読み込むようにしています。

# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :load_commentable

  def index
    @comments = @commentable.comments
  end

  def new
    @comments = @commentable.comments.new
  end

  def create
    @comment = @commentable.comments.new(comment_params)
    if @comment.save
      redirect_to @commentable, notice: "コメントしました"
    else
      render :new
    end
  end

  private
    # URLからEventかArticleを取得する
    # ex: /events/:id/comments
    # ex: /articles/:id/comments
    def load_commentable
      resource, id = request.path.split('/')[1, 2]
      @commentable = resource.singularize.classify.constantize.find(id)
    end

    # 他の方法
    # def load_commentable
    #   klass = [Event, Article].detect { |c| params["#{c.name.underscore}_id"] }
    #   @commentable = klass.find(params["#{klass.name.underscore}_id"])
    # end

    def comment_params
      params ...
    end
end


以上です。

参考文献