【Rails】投稿へのコメント機能を分かりやすく解説する。

<h1>投稿へのコメント機能をいれたい</h1>

※ 前提として投稿機能を作成している必要があります ※　
※ 投稿機能についてはこの記事では解説してません ※　

投稿に対するコメント機能の実装を行いたい！
けど、初心者なのでどうやったらいいのかわからない。
カリキュラムを見たけどなんだか複雑でわからない。

そんな方のため投稿へのコメント機能を分かりやすく解説します＾＾

イメージとしてはこんな機能ができます。

イメージ図：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1810522/46e5e28b-da84-a1fe-1bfc-ef9b2fe1edf1.png)

とある投稿に対して、コメントがついていますね！

それでは、いきます！

<h2>必要になる項目</h2>

投稿へのコメント機能に必要になるファイルはコチラ。

===============================

ステップ1：マイグレーションファイル
ステップ2：モデル
⇒ アソシエーションにつかいます

ステップ3：ルーティング
⇒ 投稿モデル等の親モデルにネストさせる必要があります。
こちらについては後ほど解説します。

ステップ4：コントローラー
ステップ5：ビューファイル

===============================


これらを順番に塔載していきます。

少し長くなるので、わからなくなったら
前に戻ったりして理解を深めてくださいね！

<h2>ステップ１：マイグレーションファイル</h2>

まずはマイグレーションファイルから記載していきましょう。

必要になるカラムは以下。
※ postモデルとuserモデルは作成済みの前提で進めます。

```php:app/config/db/migrate/該当のマイグレーション.rb

class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.integer :user_id, null: false
      t.integer :post_id, null: false
      t.text :content, null: false
      t.timestamps
    end
  end
end

```

それでは解説をしていきます。

user_id ⇒ 投稿に対してコメントを書いた人のuser_id
post_id ⇒ 投稿のid
content ⇒ 投稿内容

post_idは後ほども解説しますが、
ルーティングでネストすることによって取得できます。

ここでは、そうなんだ～程度に覚えといてください。

完了してら、rails db migrateをしてテーブルを
作成しちゃってくださいね♪

これでテーブルは作成完了！

<h2>ステップ２：モデル</h2>

次はアソシエーションをするためにモデルの記述をしていきます！

投稿モデルは３つのモデルに記述が必要になります。

１：投稿者のモデル(当記事ではUserモデル)
２：投稿モデル(コメントする記事の大元のモデル)
３：コメントモデル(投稿に対するコメントしたモデル)

・
・
・

それでは、アソシエーションを書いていきます。

とその前に全体の繋がりをER図で確認していきます！
（この図では会員・投稿・コメントの赤文字部分が対象のテーブルでアソシエーションでつないでいきます。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1810522/64716e18-97f7-e6c1-3863-b9151ae2bf77.png)


```php/app/models/user.rb

  has_many :posts, dependent: :destroy
  has_many :comments, dependent: :destroy

#ユーザーモデルは親の要素あたり１体多の関係に
#postモデルとcommentモデルの間にありますので
#dependet: :destroyです。

#つまりuserモデルが削除されると、投稿も消えるしコメントも
#削除されてしまうということなのです！

```

```php/app/models/user.rb

#postモデルへのアソシエーション
  has_many :posts, dependent: :destroy

#commentモデルへのアソシエーション
  has_many :comments, dependent: :destroy

#ユーザーモデルは親の要素あたり１体多の関係に
#postモデルとcommentモデルの間にありますので
#dependet: :destroyです。

#つまりuserモデルが削除されると、投稿も消えるしコメントも
#削除されてしまうということなのです！

```

```php/app/models/post.rb

 # userのアソシエーション
  belongs_to :user

  # 投稿のコメントへのアソシエーション
  has_many :comments, dependent: :destroy

#postsは親のモデルがuserなので、userがbelongs_toで
#１つの投稿にはたくさんのコメントがつくのでhas_manyとなる！

```

```php/app/models/comment.rb

  belongs_to :user
  belongs_to :post

#commentは親のモデルがuserとpostなので、
#userとpostがbelongs_toになります。

```

モデルに記述するアソシエーションはこの３つ書けばOKです！

<h2>ステップ３：ルーティング</h2>

ルーティングは少しざけ工夫が必要になります。
ネストという技をつかうわけなんですね。

まずは、どんなもんかお見せします！

```php/app/config/routes.rb

    # devise用のurl
    devise_for :users
  
    # users用のURL用に設定
    resources :users

    resources :posts do
      resources :comments, only: [:create, :destroy]
    end

```

見てわかる通り、postの中にdoとendで囲ってcommentsが入っていますね。

こういう記述をすることでpostのurlの中にcommentのurlも含められるんです。

この記述はコントローラーでの投稿データのidを取得するのにも必要になるのですが、commentのurlはpostに紐づいていますよ！ということもurlで確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1810522/70c41143-5f6c-5ce5-3a7a-af14fb1308f6.png)

rails infoのurlパターンでみるとわかりやすいですが、
urlが下記のようになっていますよね！
commentsに対してどの投稿idか？ということがurlに含まれるので
ネストする書き方をします。

/posts/:post_id/comments(.:format)


<h2>ステップ４：コントローラー</h2>

モデルとルーティングとマイグレーションファイルの記述が終わりましたので
いよいよコントローラーで処理を記載していきます！


まずは、コントローラー内の処理をみていきましょう♪

```php/app/controllers/comments_controller.rb
class CommentsController < ApplicationController

  def create
    @post = Post.find(params[:post_id])
    @comment = Comment.new(comment_params)
    @comment.user_id = current_user.id
    @comment.post_id = @post.id
    @comment.save
  end

  def destroy
    @post = Post.find(params[:post_id])
    @post_comments = @post.comments
    Comment.find_by(id: params[:id], post_id: params[:post_id]).destroy
  end

  private

  def comment_params
    params.require(:comment).permit(:content)
  end
end

```

コントローラーのcreateから見ていきましょう！

まず大前提として、createする際にcommentテーブルの書きをすべて
データベースに登録しなければいけません。


```php:app/config/db/migrate/該当のマイグレーション.rb

class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.integer :user_id, null: false
      t.integer :post_id, null: false
      t.text :content, null: false
      t.timestamps
    end
  end
end

```

use_idとpost_idとcontentを埋めるための記述をcreateでします。
詳しく解説していきます！


```php/app/models/user.rb
  def create
    @post = Post.find(params[:post_id])
　　⇒ アクセスした投稿のurlからpost_idを取得します。
　　例えば、投稿記事のurlが/posts/6であるとcommentを作成するときに
　　id6の記事を探し出してきます。

    @comment = Comment.new(comment_params)
　　⇒ 投稿前には.newでカラムの箱を用意しないといけないので
    　　　ストロングパラメーターのcomment_paramsを記述してcontentの空のカラムを用意します。    

    @comment.user_id = current_user.id
　　⇒ commentカラムにはuser_idがあるので現在ログインしているユーザーのidを
　　　current_user.idでuser_idカラムに代入します。

    @comment.post_id = @post.id
　　⇒ commentカラムにはpost_idがあるので現在アクセスしている
　　　記事のidをpost_idに登録したいので@postですでに投稿のidを取得しているので
　　　投稿記事自体のidだけをpost_idで格納してあげます。

　　　ちなみにfindメソッドは今回の場合ですと、投稿記事自体のidだけでなく
　　　すべての情報つまり、投稿テーブル(今回は記述をこの記事では省いてます)の投稿内容やuser_id
　　　など引っ張ってきているので今回は投稿のidだけが必要なので@post.idと書くことで
　　　idだけを格納することができるようになってます！

　  @comment.save
　　最後に@comment.saveと書くことでデータを保存できます。

    end

    def destroy
    @post = Post.find(params[:post_id])
    削除する場合もどうようにまずは投稿記事のidを探します。
    Comment.find_by(id: params[:id], post_id: params[:post_id]).destroy
    そして、削除する際は少し工夫が必要です。
    思い出してださい。commentテーブルにはuser_id, post_id, contentがあったはずです。
    それらのうちuser_idとpost_idを指定して削除する必要があります。
　
    なのでid: params[:id]というのはコメントのidで、params[:post_id]というのは
    投稿記事のidを記述しています。　
　　end

   private
   def comment_params
   params.require(:comment).permit(:content)
    ちなみにストロングパラメーターですが、:user_idと:post_idがないじゃん？
    お気づきになった方もいるかもしれませんが、実はcreateのコントローラー内で
    @comment.user_id = current_user.idと@comment.post_id = @post.idを記載しており
    ストロングパラメーターに書かなくてもOkなんです。
    ストロングパラメータに書かないといけないのはフォームから飛んできたデータには
    書かないといけませんが、user_idとpost_idはフォームから飛んでこないので書かなくてもOK。
    ※書いても作動はします。
    end

end
```

以上がコントローラー内の記述となります！


```php/app/views/posts/show.html.erb

<%= form_with model:[@post, @comment], local: false do |f| %>
 <%= f.text_area :content, rows:'5',placeholder: "Any comments on this post?", class:"form-control mt-3"  %>
   <div class="text-right">
    <%= f.submit "Submit", class: "btn btn-primary mt-3 mb-3" %>
   </div>
<% end %>

form_withする際のインスタンスは@postと@commentの両方が必要になります。
これは、@postは投稿のインスタンス変数で@commentはコメントのインスタンス変数になります。
postコントローラーのshowアクションに記載するコードは下記の通り。

@post = Post.find(params[:id])
@comment = Comment.new


<% @post_comments.each do |comment| %>
 <div class="col-3 text-center">
   <%= attachment_image_tag comment.user, :profile_image, fallback: "no_image.jpg", size: '40x40' , class:"rounded-circle"; %>
    <div class="small mb-4"><%= comment.user.nickname %>
   </div>
　</div>
<%= link_to "Delete", post_comment_path(comment.post, comment), method: :delete, remote: true, "data-confirm" => t('comments.create.delete_confirm'), class: "btn-sm btn-danger" %></td>
<% end %>

@post_commentsというのは、投稿に紐づくコメントをすべて取得するためのインスタンス変数。
なのでpostモデルにインスタンス変数として、@post_comments = @post.commentsようにpostコントローラーに記述が必要です。
※ commentsはアソシエーションで@postに紐づくすべてのコメントを取得しています。

それをeach文で回すことで投稿に対するコメントをひとつずつ表示できます。

削除する際はpost_comment_pathに記事のidとコメントのidが二つ必要になってきます。なのでコメントのidはcommentで取得でき
投稿のidはアソシエーションを使って、commentに紐づくpost(投稿のid)を二つ記載すればＯＫ。

これでpost_idとcomment_idを特定できて削除できます！

```

<h2>まとめ</h2>

いかがでしたでしょうか？

投稿へのコメントはルーティング、コントローラー、ビューファイル内で少し
工夫した書き方が必要になってきますが理屈を理解できれば簡単になると思います。

もしわからない箇所や誤りがありましたらコメントくださいね！

それでは、チャオ！