【Rails】コメント機能の導入

# 目的
Railsで作成したアプリにコメント機能を導入する。

# 開発環境
macOS: *Big Sur*
Rubyバージョン: *2.6.5*
Railsバージョン: *6.0.0*

# 前提
- アプリ`test-app`が作成されている。
[【Rails】簡単な投稿アプリの作成](https://qiita.com/oak1331/items/18a4f3a3386aab0fa354)
- `devise`が導入されている。
[【Rails】ユーザー管理機能(devise)の導入](https://qiita.com/oak1331/items/0759bd297e9fd3b81ed2)

# 手順

1. [Commentモデルの作成](#commentモデルの作成)
1. [アソシエーションの設定](#アソシエーションの設定)
1. [バリデーションの設定](#バリデーションの設定)
1. [ルーティングの設定](#ルーティングの設定)
1. [comentsコントローラーの作成・編集](#comentsコントローラーの作成編集)
1. [postsコントローラーの編集](#postsコントローラーの編集)
1. [ビューファイルの編集](#ビューファイルの編集)

# 前置き

今回はRailsで作成したアプリにコメント機能を導入します。
具体的には、ユーザーが投稿した投稿詳細ページにコメントを追加、削除できる機能です。

# Commentモデルの作成

それでは早速始めていきます！
まず`Commentモデル`を作成します。

```:ターミナル
% rails g model comment
```

コメント内容は、`textカラム`としています。

```db/migrate/20XXXXXXXXXXXX_create_comments.rb
class CreateComments < ActiveRecord::Migration[6.0]
  def change
    create_table :comments do |t|
      t.text       :text, null: false
      t.references :user, null: false, foreign_key: true
      t.references :post, null: false, foreign_key: true
      t.timestamps
    end
  end
end
```

マイグレーションを編集し、マイグレートしましょう!

```:ターミナル
% rails db:migrate
```

以上でモデルは完成です。

# アソシエーションの設定

次にアソシエーションを設定していきます。

```ruby:app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :post
end
```

```ruby:app/models/user.rb
class User < ApplicationRecord
#省略
  has_many :comments, dependent: :destroy
end
```

```ruby:app/models/post.rb
class Post < ApplicationRecord
#省略
  has_many :comments, dependent: :destroy
end
```

以上でアソシエーションの設定ができました!

# バリデーションの設定

続いてバリデーションの設定です！

```ruby:app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :post

  validates :text, presence: true
end
```

コメントが空では保存できないようにします。

# ルーティングの設定

次はルーティングを設定します!
コメントには必ず投稿が紐づいているため、ルーティングをネストさせます。

```ruby:config/routes.rb
Rails.application.routes.draw do
#省略
  resources :posts do
    resources :comments, only: [:create, :destroy]
  end
#省略
end
```

コメントは`作成機能`と`削除機能`を実装します!

# comentsコントローラーの作成・編集

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

```:ターミナル
% rails g controller comments
```

続いてアクションの定義も行います!
ここで、保存が出来ない場合の処理についても実装していきます。
今回は、renderを用いて詳細ページのビューファイルを表示するようにします。

```ruby:app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :authenticate_user!

  def create
    @comment = Comment.new(comment_params)
    if @comment.save
      redirect_to post_path(@comment.post)
    else
      @post = @comment.post
      @comments = @post.comments.includes(:user)
      render "posts/show"
    end
  end

  def destroy
    comment = Comment.find_by(id: params[:id], post_id: params[:post_id])
    comment.destroy
    redirect_to post_path(comment.post)
  end

  private
  def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, post_id: params[:post_id])
  end
end
```

`destroyアクション`内ですが、ルーティングでネストさせているため、引数は2つ指定します。
これでコメント作成の機能はできたので、
次にコメントを表示するための実装をしていきます!

# postsコントローラーの編集

まずpostsコントローラーの編集です。
コメントは投稿詳細ページに表示していくため、showアクション内に追記していきます。

```ruby:app/controllers/posts_controller.rb
class PostsController < ApplicationController
#省略
  def show
    @post = Post.find(params[:id])
    @comment = Comment.new
    @comments = @post.comments.includes(:user)
  end
#省略
end
```
`includes`でN+1問題も解決しています。


# ビューファイルの編集

最後にビューファイルの編集です！
`コメント送信フォーム`と`コメント一覧の表示`を実装していきます。

```:app/views/posts/show.html.erb
<% if user_signed_in? %>
  <%= form_with model: [@post, @comment], local: true do |form| %>
    <%= form.text_area :text, placeholder: "コメントする", rows: "2" %>
    <%= form.submit "送信" %>
  <% end %>
<% else %>
  <strong><p>コメントの投稿には新規登録/ログインが必要です</p></strong>
<% end %>

<h4>＜コメント一覧＞</h4>
<% @comments.each do |comment| %>
  <p>
    <strong><%= comment.user.name %>：</strong>
    <%= comment.text %>
    <% if user_signed_in? && current_user.id == comment.user_id %>
      <%= link_to "削除", post_comment_path(comment.post.id, comment.id), method: :delete %>
    <% end %>
  </p>
<% end %>
```

条件分岐で、ログインしていない場合はコメントを投稿できないようにしています。

コメント一覧表示では、`コメント`と`コメントしたユーザー名`を表示しています。
また、コメント投稿者のみ`削除リンク`が表示されるようにしています。

# 最後に

以上でコメント機能の追加ができました。
CSSを実装していないため、簡素な見た目となっています。
必要に応じて各自、編集をお願いします。
では。