5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsのポリモーフィック関連について

Last updated at Posted at 2020-08-15

ポリモーフィックについて

今回はポリモーフィック関連について書いていこうかと思います。
ポリモーフィックとは複数の異なる型やオブジェクトに対して共通のインターフェースを備えることです。 ポリモーフィック関連付けを使用すると、ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現することができます。

例えば、映画レビューサイトを作る場合、Movieモデル、Actorモデル、Commentモデルを用意して作品や出演者のレビューコメントが投稿できるようにコメントモデルには中間テーブルを作成し、それぞれ関連付けを行う必要があるかと思います。

この場合、モデルが増える(監督についてもモデルを作ってレビューコメント機能をつけたい等)たびに、中間テーブルを用意し関連付けをする必要があり非常に面倒です。
そこで共通のインターフェースを用意して、それにCommentを関連付けて一つのモデル(comment)だけで処理をしようというのが、ポリモーフィック関連です!

modelの作成

ポリモーフィック関連付けの実装自体はいたってシンプルです。

まずはターミナルでcommentモデルを作成します。

rails g model comment content:text commentable:references{polymorphic}

ポイントはcommentable:references{polymorphic} 

マイグレーションファイルは下記のようになります。

..._create_comments.rb
class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.text :content
      t.references :commentable, polymorphic: true

      t.timestamps
    end
  end
end

カラムはcontent以外に自動でcommentable_typeとcommentable_idが生成されます。
commentable_typeにはMovieもしくはActorのモデル名が入ります。
commentabel_idはMovieもしくはActorのidが入ります。
commentable_typeではどのモデルに対してのcommentなのか、commentable_idではそのモデルのどのidに対してのcommentなのかを判別するカラムとなります。

ポリモーフィック関連付け(アソシエーション)

db:migrateでモデルが作成できれば、それぞれのモデルに関連付けを行なっていきます。

Movie.rb
has_many :comments, as: :commentable
Actor.rb
has_many :comments, as: :commentlable
Comment.rb
belongs_to :commentable, polymorphic: true

以上で関連付けは終了です。

あとcontrollerとroutes、view側はポイントを絞って解説します。

controllerの処理

まずはcontroller側ですが、commentsはネストされている状態なので、commentの処理をする際にはMovieかActorのid(どちらに紐づいているコメントかを判別)が必須になります。単独のネストのid取得だとcomments_controllerでMovie.find(params[movie_id])等で設定が可能ですが、ポリモーフィックの場合はMovieかActorかが不明なので、両方に対応した書き方が必要です。

まずcontrollersにmovieとactorのフォルダを作成し、その直下にそれぞれcomments_controller.rbを作成し、CommentsControllerを継承しているMovie::CommentsControllerとActor::CommentsControllerを用意して、private以下にそれぞれのidを取得する実装をします。

/app/controllers/movies/comments_controller.rb
class Movie::CommentsController < CommentsController  
  before_action :set_commentable, only: %i[create]  
  private  
    def set_commentable  
      @commentable = Movie.find(params[:movie_id])  
    end  
end  
/app/controllers/actors/comments_controller.rb
class Actor::CommentsController < CommentsController  
  before_action :set_commentable, only: %i[create]  
  private  
    def set_commentable  
      @commentable = Actor.find(params[:actor_id])  
    end  
end  

before_actionでset_commentableをすることによって@commentable(movie_idかactor_id)が動的に取得できるようになります。
例えば、以下のcommentのcreate時に状況によってそれぞれ(movieもしくはactor)のモデルに紐づくcommentインスタンスをbuild生成することができます。

app/controllers/comments_controller.rb
class CommentsController < ApplicationController

  def create
    @comment = @commentable.comments.build(comments_params)  
    if @comment.save
        .
        .

また独自の処理をさせたい場合は、オーバーライドすることも可能です。

/app/controllers/actors/comments_controller.rb
class Actor::CommentsController < CommentsController  
  before_action :set_commentable, only: %i[create]

  def create
    super
    flash[:notice] = 'actorのコメントを送信しました!' 
  end

  private  
    def set_commentable  
      @commentable = Actor.find(params[:actor_id])  
    end  
end  

routesについて(module)

上記のcontrollerファイル構成変更に伴い、routesのmoduleの記述が必要です。
これでbefore_action :set_commentableが反映されます。

routes.rb
  resources :movies do  
    resources :comments, only: [:create], module: :movies  
  end  
  
  resources :actors do  
    resources :comments, only: %i[create], module: :actors 
  end  

viewについて

最後に上記で説明した部分に関わってくるview側の記述を書いておきます。

app/views/movies/show.html.erb
<%= @movie.name %>
<%= render 'comments/comments', commentable: @movie, comment: @comments %> 
app/views/actors/show.html.erb
<%= @actor.name %>
<%= render 'comments/comments', commentable: @actor, comment: @comments %>
app/views/comments/_form.html.erb
<%= form_with model: [commentable, Comment.new] do |f| %>  
  <%= f.text_area :content %>  
  <div class="actions">  
    <%= f.submit "コメントをする", class: 'btn btn-warning px-5' %>  
  </div>  
<% end %>

form_withのcommentableには@movie@acotrが入っているため、ここでmovie側のcommentかactor側のcommentかを振り分けています。

以上、終わりです。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?