[Rails] コメント機能実装

# はじめに
コメント機能を実装するための忘備録です。
Userモデル、Articleモデル(Postモデルと同じ）は作成済みで行っています。
# 実行環境
- Rails 7.0.4.1
- Ruby 3.0.4
- Devise 4.8.1

# 実装
### 1. アソシエーション
![名称未設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118527/42b97f6f-d233-1379-c1f6-ceeef6e8826c.png)
articlesテーブルとcommentsテーブルを1対多でつなぎ、comment_contentのレコードを追加していくだけでコメントを残すことは出来そうですが、
今回は、***誰がコメントを追加したか***まで実装したいため、articlesテーブルだけでなく、usersテーブルもcommentsテーブルにつなげます。

この場合、usersテーブルとariticlesテーブルは***多対多***の関係になっており、commentsテーブルはそれを実現させる為の関係テーブルです。

まとめると、
&emsp;・usersとcommentsは1対多の関係
&emsp;・articlesとcommentsは1対多の関係
&emsp;・usersとariticlesは多対多の関係　
になっています。

### 2.モデルの作成
```
$ rails g model Comment comment_content:text user:references post:references
```
データベースへ反映。
```
$ rails db:migrate
```
### 3.マイグレーション
Userモデル:
```ruby:app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :articles, dependent: :destroy
  has_many :comments, dependent: :destroy  #userの削除と同時に、commentも削除される
end
```
Articleモデル：
```ruby:app/models/article.rb
class Article < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy  #articleの削除と同時に、commentも削除される
end
```
Commentモデル:
```ruby:app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :article
  validates :user_id,    presence: true
  validates :article_id, presence: true
  validates :comment_content, presence: true  #空のコメントを送信できないようにする
end
```
### 4.コントローラの作成
コントローラなので複数系を使って作成します。頭文字は大文字でも小文字でも平気なようです。
```
$ rails g controller posts index show
$ rails g controller comments
```

articlesコントローラ:
```ruby:app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :authenticate_user!, only: %i[show create destroy]
  before_action :correct_user, only: :destroy

  def index
    @articles = current_user.articles.all
    @article = current_user.articles.new
  end

  def show
    @article = Article.find(params[:id])
    @comment = Comments.new
    @comments = @article.comments
  end
  .
  .
end

```
今回は、コメント機能の作成のため記事の作成方法は省略します。
viewでarticleのデータとコメントのデータを使いたいので、インスタンス変数で宣言します。(@記号で始まる変数)

commentsコントローラ:
```ruby:app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :authenticate_user!
  before_action :correct_user, only: :destroy

  def create
    @comment = current_user.comments.create(comment_params)
    redirect_to request.referer || root_url
  end

  def destroy
    @comment.destroy
    redirect_to request.referer || root_url, status: :see_other
  end

  private

  def comment_params
    params.require(:comment).permit(:comment_content, :article_id)
  end

  def correct_user
    @comment = current_user.comments.find_by(id: params[:id], article_id: params[:article_id])
    redirect_to root_url, status: :see_other if @comment.nil?
  end
end
```
Deviseのcurrent_userで、今ログインしているユーザーを使えます。
コメントの削除をする際には、viewでも設定するのですが、ログインしているユーザーがしたコメントと、削除するコメントのユーザーが同じか確認できるようにしています。
また、rails7 では、destroyアクションの後に、status: :see_other 付ける必要があります。
### 5.ルーティングの設定
```ruby:config/routes.rb
resources :articles do
  resources :comments, only:[:create, :destroy]
end
```
do..endで、ルーティングを行うと親子関係が作成できます。

$ rails routes を実行すると、
```
article_comments POST   /articles/:article_id/comments(.:format)       comments#create
article_comment  DELETE /articles/:article_id/comments/:id(.:format)   comments#destroy
```
/postsの中にcommetnsがあることが確認できます。この/postsの中にcommetnsがあることが確認できます。この関係を***ネスト***と言そうです。
DELETEのルーティングをみると、リクエストを送信するのに articleのid, commentのid それぞれが必要になることが分かります。
### 6.viewの作成

```views/articles/show.erb
.
.
    <div class="comments">
      <h2>コメントをする</h2>
        <%=  form_with(model:[@article, @comment], local: true) do |f| %>
          <%= f.text_area :comment_content %>
          <%= f.hidden_field :article_id, value: @article.id %>
          <%= f.submit 'コメントする' %>
        <% end %>
    
      <h2>コメント一覧</h2>
        <span>
        <% @comments.each do |comment| %>
        <div class="comments-index">
          <%= comment.user.name %>
          <%= comment.comment_content %><br>
          Commented <%= time_ago_in_words(comment.created_at) %>前
          <% if current_user === comment.user %>  # ログインしているユーザーと、コメントがそのユーザーのものかの確認
          <div class="comment-delete">
            <%= link_to "delete", article_comment_path(comment.article, comment), data: { "turbo-method": :delete,
                                                      turbo_confirm: "You sure?" } %>
          </div>
        </div>
          <% end %>
        <% end %>
        </span>
    </div>
.
.
```
articlesコントローラでshowの中に、`@article`, `@comments`, `@comment`を宣言しているので、viewで使うことができます。

form_with(model:[@article, @comment], method: :post) となっていますが、
先程の***ネスト***されたルーティングへ、リクエストを送る際には、articleとcomment両方の情報が必要になるからです。

form_withについて:
>So when passing such a model record, Rails infers the URL and method.
>```
><%= form_with model: @post do |form| %>
> ...
><% end %>
>```
>is then equivalent to something like:
>
>```
><%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
>  ...
><% end %>
>```
>https://api.rubyonrails.org/v7.0/classes/ActionView/Helpers/FormHelper.html#method-i-form_with:~:text=So%20when%20passing%20such%20a%20model%20record,patch%20do%20%7Cform%7C%20%25%3E%0A%20%20...%0A%3C%25%20end%20%25%3E

# 参考にさせていただいた記事
https://qiita.com/ramuneru/items/db43589551dd0c00fef9

https://qiita.com/kurawo___D/items/d2fefdd329f5310113aa

https://api.rubyonrails.org/v7.0/classes/ActionView/Helpers/FormHelper.html#method-i-form_with