【Ruby on Rails】コメント機能を初心者なりにまとめてみた（非同期）


#対象者

* コメント機能を実装予定の方
* 非同期通信（Rails×Ajax）を実装予定の方


#目的

* 記事へのコメントを非同期で投稿・削除ができるようにする

#実際の手順と実例
###1.前提

* jQueryの読み込みができている
* バージョン：Ruby：2.3.0、Rails：5.2.5です。
* Articleモデル、Userモデル実装済み


###2.モデルの作成とテーブル設定

まずコメントモデルを作成します。
UserとArticleとアソシエーションを組みます。

````
$ rails g model comment user:references article:references
````

その後、config/db/schema.rbにマイグレーションファイルが作成されているので、下記を追記していきます。

`````migration_file
class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.text :comment_content　#追記
      t.references :user
      t.references :article, foreign_key: true

      t.timestamps
    end
  end
end
````

上記を確認して、**rails db:migrate**を実行すると
schemaファイルに下記が作成されます。

```scheme.rb
  create_table "comments", force: :cascade do |t|
    t.text "comment_content"
    t.integer "user_id"
    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"
    t.index ["user_id"], name: "index_comments_on_user_id"
  end
```

その後各モデルを確認します。

```app/model/comment.rb
  belongs_to :user
  belongs_to :post
  validates :comment_content, presence: true
```

Commentがないと投稿できないようにバリデーションを設定しました。

```app/model/user.rb
  has_many :posts, dependent: :destroy
  has_many :comments, dependent: :destroy
```
```app/model/article.rb
  belongs_to :user
  has_many :comments, dependent: :destroy
```

※ dependent: :destroyでアソシエーションを組んでるものが削除されたとき、それに合わせて削除されるように設定

User：Comment　＝　１：N
ユーザー１人に対してたくさんの投稿を持てる

Article：Comment　＝　１：N
記事１つに対してたくさんの投稿を持てる


###3.コントローラーとルーティングの設定

ルーティングはネストして設定します

```route.rb
  resources :posts do
    resources :comments
  end
```

下記コマンドでController作成します。

```
$ rails g controller comment
```
出来上がったControllerに下記を追加していきます。

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

  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.build(comment_params) #①
    @comment.user_id = current_user.id
    if @comment.save
     redirect_to request.referer　#②
    else
     redirect_to request.referer
    end
  end

  def destroy
    @comment = Comment.find(params[:id])
    if @comment.destroy
     redirect_to request.referer
    else
     redirect_to request.referer
    end
  end

  private

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

end
```

①　.buildメソッドとは親モデルに属する子モデルのインスタンスを新たに生成したい場合に使うメソッドです。外部キーに値が入った状態でインスタンスが生成できます。

②元のページにリダイレクトしてくれます。

https://qiita.com/nao0725/items/54ed720300370dc25dc8


###4.投稿のControllerとViewの設定

今回はarticleに対してCommentを残すので、
articles_controllerに記載します。

````app/controller/articles_controller.rb
  def show
    @comment = Comment.new
    @comments = @article.comments
  end
```

```app/views/articles/show.html.erb
  <div id = "comment_area"> #①
      <%= render "comments/form", comment: @comment %>
  </div>

  <div>
     <%= render "comments/index", comments: @comments, article: @article %>
  </div>
```

①のidをターゲットにして、このdiv内をAjaxで書き換えます。（後述）

また、部分テンプレートを使用して、投稿フォームと投稿一覧を同じページに表示します。その２つのViewを作成して、下記の内容を追記します。

`````app/views/comments/_form.html.erb
<div class="form-group row">
  <%= form_with(model: [@article, @comment]) do |f| %>

    <%= f.label :comment_content,"コメント", class:"form-label" %>

    <%= f.text_area :comment_content %>

    <%= button_tag type: "submit", class: "btn btn-default" do %>
      <i class="far fa-comment-alt"></i>
    <% end %>　#①

<% end %>
`````
①ではFontAwesomeを使用したのでこのような表記になっています。

https://qiita.com/nao0725/items/63eb73d3851c843b68bb


```app/views/comments/_index.html.erb
<% @comments.each do |comment| %>
  <p><%= comment.comment_content %></p>
  <% if comment.user == current_user %>
      <p><%= link_to  "Destroy", article_comment_path(comment.article_id, comment.id), method: :delete, remote: true %></p>
  <% end %>
<% end %>
````

* ログインしているユーザーだけがCommentを削除できるようにif 文を使ってます。
* remote: trueで非同期通信に対応しています。


###5.非同期通信の設定

今回はcreateとdestroyを非同期設定していきます。

app/views/commentsのフォルダ内に下記2つのjsファイルを作成し、追記します。

````app/views/comments/create.js.erb
$("#comments_area").html("<%= j( render "comments/form", comment: @comment ) %>")
$("textarea").val('')
````

````app/views/comments/destroy.js.erb
$("#comments_area").html("<%= j( render "comments/form", comment: @comment ) %>")
$("textarea").val('')
````

両方同じ内容です。
$("textarea").val('')の記述は、コメント入力後のコメント入力欄を空にしています。


これで非同期のComment実装完了です！！

#参照

>[Ajaxを用いた動的なコメント投稿・削除機能の実装で学ぶRuby on Rails](https://ysk-pro.hatenablog.com/entry/2018/02/10/101739)
>[buildメソッドについて](https://qiita.com/tsuchinoko_run/items/d671ea840bc0bfa90186)

####投稿者コメント
ここまで長い記事初めて書きました。
結構たいへんでしたが、自分で書いていてかなり理解を深めることができたのではないかと思います。

####My Profile
プログラミング学習歴３ヶ月目のアカウントです！
プログラミングスクールで学んだ内容や自分が躓いた箇所等のアウトプットの為に発信しています。
また、プログラミング初学者の方にわかりやすく、簡潔にまとめて情報共有できればと考えています。
もし、投稿した記事の中に誤り等ございましたら、コメント欄でご教授いただけると幸いです。