STIとポリモーフィックを利用したコメント機能の作成

#はじめに
インターン先でSTIとポリモーフィックを利用したコメント機能を作成したときに
自分が求めてる情報が出てこなかったのと
ハマったことがいくつかあるのでメモ代わりに残していこうと思います:relieved:

ポリモーフィックやSTIについては
[Railsのポリモーフィック関連とはなんなのか](https://qiita.com/itkrt2y/items/32ad1512fce1bf90c20b)
[みんなRailsのSTIを誤解してないか!?](https://qiita.com/yebihara/items/9ecb838893ad99be0561)
などがとても丁寧でわかりやすく参考になります:open_hands:

今回は設計やコード中心なので概念などはそちらで理解していただければと思います:relaxed:
私も未熟なところがたくさんあるので、指摘等あれば教えていただけると嬉しいです:relaxed:

##設計について
今回の設計はざっとこんな感じです。
（いろいろ省いて主要部分だけの実装を行っていきます:man_tone1:)

![ER図 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315790/8b3d50e1-f065-7ace-29a9-8a9508e0ae6c.png)

１つのcommentsテーブルに２種類のテーブルがSTIで継承されており
taskかprojectのどちらかが紐づくような設計となっております！:eyes:
常にprojectとtaskに紐づくcommentを作るわけではないので
projectとtaskからのびるcommentへのリレーションは点線で記述しました:fist:
（正しい書き方があるのかわからなかったです:sweat:）

##実装
早速実装に入っていきます！

###テーブルの作成
commentsとtasksとprojectsテーブルの作成を行っていきます:relieved:
commentsテーブルにはSTIのための*type*を作成し
ポリモーフィックのための*commentable_type*と*commentable_id*という〇〇_typeと〇〇_idというカラムも作ります:punch:

```terminal
$ rails g model Comment content:string type:string commentable_type:string commentable_id:integer
$ rails g model Task title:string content:string 
$ rails g model Project name:string 
```

###モデルの設定

モデルのディレクトリ構造は写真のようにcommentをtaskとprojectが継承するように作成します:runner_tone1:
<img width="208" alt="スクリーンショット 2019-05-29 18.01.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315790/41452b4e-ee00-ba63-4f20-9193494d0e65.png">

こうすることによりSTIを行うことができます:raised_hands:
次はモデルにポリモーフィックなどの設定を行っていきます:point_up:

```ruby:comment.rb
class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end
```

```ruby:project.rb
class Project < ApplicationRecord
  has_many :comments, class_name: 'Comment::Project', as: :commentable, dependent: :destroy
end
```
```ruby:task.rb
class Task < ApplicationRecord
  has_many :comments, class_name: 'Comment::Task', as: :commentable, dependent: :destroy
end
```
commentを継承しているprojectとtaskは

```ruby:.rb
class Comment::Task < Comment
end
```
```ruby:.rb
class Comment::Project < Comment
end
```
と記述することにより、commentを継承してくれます:astonished:
モデルに記述することは以上です:slight_smile:

###ポリモーフィックとSTIができているかの確認
この時点でrails consoleで以下のコマンドたちを打ってみてください！:point_down:

```terminal
 pry(main)> Comment::Task.new
=> #<Comment::Task:0x00007fbf75f1e668 id: nil, type: "Comment::Task", commentable_type: nil, commentable_id: nil>
 pry(main)> @task = Task.create(title: "test", content: "test")
 pry(main)> @task.comments.build
=> #<Comment::Task:0x00007fbf7b0ba418 id: nil, type: "Comment::Task", commentable_type: "Task", commentable_id: 4, content: nil, created_at: nil, updated_at: nil>
```
typeには継承しているmodelが入っていおり
commentable_typeにはcommentに紐づくmodelが入り
commentable_idにはcommentに紐づくidが入っております:thumbsup:
こうすることにより、commentという１つのモデルに２つのうちどちらか１つのモデルに紐づけることができます:clap:
この３つのカラムが作成できていたら、ポリモーフィックとSTIができています:sparkles:

###ルーティングとcontrollerの設定

```ruby:routes.rb
Rails.application.routes.draw do
  resources :tasks do
    resources :comments, only: [:create], controller: 'task/comments'
  end
  resources :projects do
    resources :comments, only: [:create], controller: 'project/comments'
  end
end
```

コントローラーはタスク用とプロジェクト用で分けて作ります:woman_tone1:
<img width="295" alt="スクリーンショット 2019-05-30 12.44.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315790/ffb3f558-3a6a-7c6e-26a5-6c76efccae34.png">

###タスクのコメント機能の作成
ここからはタスクのコメント機能について作成していきます:muscle:
まずはcontrollerです:open_hands:

```ruby:controlelrs/tasks_controller.rb
@comment = Comment::Task.new
```
Comment::Taskのインスタンスを作成してtasksの詳細ページに投稿画面を作成します:v:

```rhtml:tasks/show.html.erb
<%= form_for @comment, url: task_comments_path(task_id: @task.id) do |f| %>
  <%= f.text_area :content　%>
  <%= hidden_field_tag :type, @comment.type %>
  <%= f.submit 'コメントを投稿する' %>
<% end %>
```

タスクを継承しているコメントControllerでcreateを行っていきます:point_down:

```ruby:controllers/task/comment_controller.rb
  def create
    @comment = @task.comments.build(comment_params)
    @comment.save
    redirect_to task_path(@task)
  end

  private

  def comment_params
    params.require(params[:type].underscore.gsub('/', '_').to_sym).permit(:content)
  end
```

パラメーターの受け取り方は[Strong Parametersを動的に設定](https://easyramble.com/assign-strong-parameters-with-array.html)を参考にしました:dancer:

実はこれだけでもうコメントの作成はできております！:clap:

##終わりに
STIやポリモーフィック、最初全然理解は出来なかったですが、実際に手を動かすことで少し理解できたような気がします。
今はtaskとprojectにしか紐付かないのですが、もっといろんなモデルに紐づくものだと
紐づくために必要な外部キーを増やさなくて良かったり
新しくコメントに関するテーブルを作らなくていいので便利だと感じました。
でもなぜそういう使い方をしている記事がヒットしなかったんだろうという疑問が残りました。
最後まで見ていただきありがとうございました！
ご指摘あればどしどしおまちしております〜！