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

## ポリモーフィックについて
今回はポリモーフィック関連について書いていこうかと思います。
ポリモーフィックとは複数の異なる型やオブジェクトに対して共通のインターフェースを備えることです。 ポリモーフィック関連付けを使用すると、ある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かを振り分けています。



以上、終わりです。