記事にコメント投稿機能・一覧表示機能・削除機能を加えるやり方について

###前提
[記事に対してコメントを投稿・詳細表示・一覧表示・編集・削除するやり方について](http://...)の続きとなります。
すでに作成してある記事詳細機能の箇所にコメント投稿欄、一覧表示、削除機能を加えていきます。

###ルーティング作成
まず最初に、必要なルーティングを作成していきます。

```ruby:route.rb
Rails.application.routes.draw do
・
・
・
  resources :articles do 
    resources :comments, only: [:create, :destroy]
  end
end

```
今回は、articleとcommentの関係性が「article:comment = 1:多(has_manyとbelongs_toの関係)」なので入れ子でルーティングを作成します。また、createアクションとdestroyアクション以外は今回使わない予定なので、`only:`をつけて制限しています。
一旦、正しくルーティングが反映されているか確認してみましょう。

```console:console
$ rails routes
Prefix Verb      URI    Pattern                                      Controller#Action
article_comments POST   /articles/:article_id/comments(.:format)     comments#create #コメント投稿
 article_comment DELETE /articles/:article_id/comments/:id(.:format) comments#destroy #コメント削除
        articles GET    /articles(.:format)                          articles#index
                 POST   /articles(.:format)                          articles#create
     new_article GET    /articles/new(.:format)                      articles#new
    edit_article GET    /articles/:id/edit(.:format)                 articles#edit
         article GET    /articles/:id(.:format)                      articles#show
                 PATCH  /articles/:id(.:format)                      articles#update
                 PUT    /articles/:id(.:format)                      articles#update
                 DELETE /articles/:id(.:format)                      articles#destroy
```
commentのcreateとdestroyのみルーティングが作成されていることや既存のarticleのルーティングが変化していないことが確認できました。


###モデルの作成
モデルを作成していきます。
今回のコメントテーブルにも、titleカラムとcontentカラムを用意していきます。

```console:console
$ rails g model Comment title:string content:text article:references
Running via Spring preloader in process 13297
      invoke  active_record
      create    db/migrate/20190702071230_create_comments.rb
      create    app/models/comment.rb
      invoke    test_unit
      create      test/models/comment_test.rb
      create      test/fixtures/comments.yml
```

`article:references`を追加することで「commentsテーブルに外部キーを設定すること」と「commentモデルにbelongs_to :article」が設定されます。

```ruby:comment.rb
class Comment < ApplicationRecord
  belongs_to :article
end
```

```ruby:db/migrate/hogehoge_create_comments.rb
class CreateComments < ActiveRecord::Migration[5.0]
  def change
    create_table :comments do |t|
      t.string :title
      t.text :content
      t.references :article, foreign_key: true 

      t.timestamps
    end
  end
end
```

問題なさそうなのでマイグレーションを実行していきます。

```console:console
$ rails db:migrate
== 20190702071230 CreateComments: migrating ===================================
-- create_table(:comments)
   -> 0.0056s
== 20190702071230 CreateComments: migrated (0.0061s) ==========================
```

```ruby:db/schema.rb
ActiveRecord::Schema.define(version: 20190702071230) do

・
・
・
  create_table "comments", force: :cascade do |t|
    t.string   "title"
    t.text     "content"
    t.integer  "article_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["article_id"], name: "index_comments_on_article_id"
  end
・
・
・
end
```

###モデル間でリレーションを作成する
articleとcommentの関係性

- **1つのarticle(記事)**は**複数のcomment(コメント)**を持つこどができる→has_many
- **1つのcomment(コメント)**は**1つのarticle(記事)**に属することができる→belongs_to

この関係性を各モデル間で表現すると以下のようになります。

```ruby:article.rb
class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
end
```

`dependent: :destroy`を加えることで記事を削除したら関連するコメントも自動で削除されます。

```ruby:comment.rb
class Comment < ApplicationRecord
  belongs_to :article
end
```

###コントローラー作成
続いてはcommentsコントローラーを作成していきます。

```console:console
rails g controller Comments
Running via Spring preloader in process 8903
      create  app/controllers/comments_controller.rb
      invoke  erb
      create    app/views/comments
      invoke  test_unit
      create    test/controllers/comments_controller_test.rb
      invoke  helper
      create    app/helpers/comments_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/comments.coffee
      invoke    scss
      create      app/assets/stylesheets/comments.scss
```
articlesコントローラーとcommentsコントローラーにそれぞれ必要な処理を実装していきます。

```ruby:/training/app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
・
・
・ 
  def show
    @article = Article.find(params[:id])
    #コメントフォームに必要なインスタンスを生成
    @comment = Comment.new
  end
  
・
・
・
```
```ruby:/training/app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    #コメントを投稿する対象のインスタンス（記事）を作成
    @article = Article.find(params[:article_id])
    #buildメソッドを用いることでcommentインスタンスにarticle_idをセットしてcommentインスタンスを作成
    @comment = @article.comments.build(comment_params)
    if @comment.save
      redirect_to @article
    end
  end
  
  
  def destroy
    #記事(article)の情報を取得
    @article = Article.find(params[:article_id])
    # その記事内の削除対象のコメントを探して取得
    @comment = @article.comments.find(params[:id])
    if @comment.destroy
      redirect_to article_path(@article)
    end
  end
  
  private
    def comment_params
      params.require(:comment).permit(:title,:content)
    end
end
```
buildメソッドは「インスタンスの作成+外部キーを設定」までやってくれますが、DBに保存まではやってくれないので、saveメソッドを使って保存する必要があります。

###ビューの作成
記事詳細画面にコメントの入力フォームやコメントの一覧を表示していきます。

```html:/training/app/views/articles/show.html.erb
<table>
    <tbody>
        <tr>
            <th>タイトル</th>
            <td><%= @article.title %></td>
        </tr>
        <tr>
            <th>内容</th>
            <td><%= @article.content %></td>
        </tr>
    </tbody>
</table>
<h2>コメントを入力</h2>
<!--引数の順番は親、子-->
<%= form_for ([@article,@comment]) do |f| %>
    <p>
        <%= f.label :title %><br/>
        <%= f.text_field :title %><br/>
    </p>
    <p>
        <%= f.label :content %><br/>
        <%= f.text_area :content %><br/>
    </p>
    <p>
       <%= f.submit "送信" %>
    </p>
<%end%>
<h2>コメント一覧</h2>
<!--@article.commentsで該当する記事のコメント一覧を取得-->
<% @article.comments.each do |comment| %>
    <p>タイトル</p>
    <p><%= comment.title %></p>
    <p>内容</p>
    <p><%= comment.content %></p>
    <!--article_comment_pathヘルパーには親(article)と子(comment)を両方渡す-->
    <p><%= link_to '削除',article_comment_path(@article,comment),
                          method: :delete,data: {confirm:'削除してもよろしいですか？'}%></p>
<% end %>
```
内容が少し冗長なのでパーシャルを用いて
入力フォームとコメント一覧の箇所をリファクタリングしていきます。

```html:/training/app/views/comments/_comment.html.erb
<h2>コメント一覧</h2>
<!--@article.commentsで該当する記事のコメント一覧を取得-->
<% @article.comments.each do |comment| %>
    <p>タイトル</p>
    <p><%= comment.title %></p>
    <p>内容</p>
    <p><%= comment.content %></p>
    <!--article_comment_pathヘルパーには親(article)と子(comment)を両方渡す-->
    <p><%= link_to '削除',article_comment_path(@article,comment),
                          method: :delete,data: {confirm:'削除してもよろしいですか？'}%></p>
<% end %>
```

```html:/training/app/views/comments/_form.html.erb
<h2>コメントを入力</h2>
<!--引数の順番は親、子-->
<%= form_for ([@article,@comment]) do |f| %>
    <p>
        <%= f.label :title %><br/>
        <%= f.text_field :title %><br/>
    </p>
    <p>
        <%= f.label :content %><br/>
        <%= f.text_area :content %><br/>
    </p>
    <p>
       <%= f.submit "送信" %>
    </p>
<%end%>
```

```html:/training/app/views/articles/show.html.erb
<table>
    <tbody>
        <tr>
            <th>タイトル</th>
            <td><%= @article.title %></td>
        </tr>
        <tr>
            <th>内容</th>
            <td><%= @article.content %></td>
        </tr>
    </tbody>
</table>
<%= render @article.comments %>
<%= render "comments/form" %>
```

# 最後に
誤っている箇所や追記した方が良い点等ございましたら
編集リクエストやコメントの方でご指摘していただけると幸いです。

参考文献
[Railsを始めたばかりの人向け！Railsの仕組みを一から理解しながらブログを作成する](https://ruby-rails.hatenadiary.com/entry/20140813/1407915718#getting-up-and-running)
[Ajaxを用いた動的なコメント投稿・削除機能の実装で学ぶRuby on Rails](https://ysk-pro.hatenablog.com/entry/2018/02/10/101739#1-%E5%AE%8C%E6%88%90%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8)
[buildモデルを生成(new/build)](http://railsdoc.com/references/build)