コメント機能実装 - 非同期通信でのコメント表示を操作する - Ajax,Ruby, Rails, jQuery, jbuilder, JSON

# はじめに
ECサイトやブログサイトでコメント機能実装を行うことがありますよね？
##### 今回はコメント機能実装を事前準備をはじめとして、コメント機能実装にまつわるテーブル、モデル、コントローラーの作成、jbuilder、JSON、jQueryの組み立て方を一つ一つ詳しく解説しながら、非同期通信(Ajax)について深堀し、コメント機能を実装する際に便利なチップを共有します！！

Twitter: https://twitter.com/f_t_b_future

### 目次
- [1開発環境](#1開発環境)
- [2実装要件](#2実装要件)
- [3事前準備](#3事前準備)
  - [3-1基本準備](#3-1基本準備)
  - [3-2コントローラーの設定](#3-2コントローラーの設定)

- [4非同期通信](#4非同期通信)
  - [4-1jbuilder解説](#4-1jbuilder解説)
  - [4-2haml解説](#4-2haml解説)
  - [4-3jsファイル解説](#4-3jsファイル解説)
     - [4-3-1jsでのビューの設定](#4-3-1jsでのビューの設定)
     - [4-3-2取得データ基の指定](#4-3-2取得データ基の指定)
     - [4-3-3JSONデータ取得(ajaxの中についてみていきます)](#4-3-3JSONデータ取得(ajaxの中についてみていきます))
     - [4-3-4取得データの処理(donefailの中についてみていきます)](#4-3-4取得データの処理(donefailの中についてみていきます))

- [5要素操作](#5要素操作)
  - [5-1beforeについて](#5-1beforeについて)
  - [5-2appendについて](#5-2appendについて)
  - [5-3removeについて](#5-3removeについて)
  - [5-4valについて](#5-4valについて)
  - [5-5propについて](#5-5propについて)

- [6まとめ](#6まとめ)

# 1開発環境
Rails 5.2.3
Ruby 2,5.1
jQuery
Haml
jbuilder

# 2実装要件

##### 順に説明していきながら以下の実装要件を解決していきます。先に内容を見たい方はリンクから当該記事をご覧ください。
[コメントがあればコメントを表示](#コメントがあればコメントを表示)
[商品が売れていなければコメント可能](#商品が売れていなければコメント可能)
[ユーザー登録をしていなければコメント不可](#ユーザー登録をしていなければコメント不可)
[商品が売れたらコメント不可](#商品が売れたらコメント不可)
[出品者がコメントしたら、ニックネームの下に出品者と表示](#出品者がコメントしたらニックネームの下に出品者と表示)
[コメントを投稿して非同期通信で表示させる際にコメントを上から古い順番に並べる](#コメントを投稿して非同期通信で表示させる際にコメントを上から古い順番に並べる)
[コメントがなければ` ＊まだコメントはありません。`と表示](#コメントがなければまだコメントはありませんと表示)
[非同期の状態でも連続してコメントを入力したい](#非同期の状態でも連続してコメントを入力したい)
[非同期の状態でも連続して送信ボタンを押したい](#非同期の状態でも連続して送信ボタンを押したい)

# 3事前準備
#### 3-1基本準備
＊[3-2コントローラーの設定](#3-2コントローラーの設定)あたりから具体的な内容になってくるので、事前準備が不要な方は[そちら](#3-2コントローラーの設定)からご覧ください。

それでは初めていきます。
まず、Gemfileにjquery-railsが記載されていることを確認しましょう。
なければ下記を記載して　bundle install　です！！


```ruby:Gemfile
gem 'jquery-rails
```
```ruby:app/assets/javascripts/applications.js

//= require jquery
//= require jquery_ujs
//= require_tree .
```

ルートはこの様にして基本的にはproductsコントローラーにネストする形でコメント機能を操作しようと思います。
これにより、コメントに紐づくproductのid情報を含んだパスの受け取りが可能になります。

```ruby:Routes.rb
  resources :products do
    resources :comments, only: [:create]
  end
```

次にコメントができる準備です。
モデルとコントローラーを作成してなければ作成します。

まずモデルです。
rails g model comment
でマイグレーションファイルとモデルファイルができますよね。
マイグレーションファイルには追加したいテーブルを記載します。

```ruby:XXXXXXXXXXXXXX_create_comments.rb
class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.integer :user_id,     null: false
      t.integer :product_id,  null: false
      t.text :text,           null: false
      t.timestamps
    end
  end
end
```
モデルにはアソシエーションとバリデーションを記載します。

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

  validates :text, presence: true
end
```

そして大事なのですが
rails db:migrate
を忘れずに！！！

次はコントローラーです。
rails g controller comments
これでコントローラーとビューのフォルダができました。

#### 3-2コントローラーの設定
まず、`comment_params`を作成します。

>paramsの中でコメントテーブルの
`user_idカラムにcurrent_user.id`及び`product_idカラムにproduct_id`
を保存し`パラメーター(comment_params)`に渡せる様にしています。

今回コメントコントローラーにはルートで定義した createを記載します。

```ruby:app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    @comment = Comment.create(comment_params)
    @sellerId = @comment.product.user_id
    respond_to do |format|
      format.html { redirect_to product_path(params[:product_id])}
      format.json
    end
  end

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

end

```
createメソッドの中には Cmment.create(comment_params)の内容を @commentに入れてあげます。

# 4非同期通信解説
### 4-1jbuilder解説
さて、ここから非同期通信の準備をしていきましょう。
コントローラー内に記載した下記のコードは respond_to doで .html もしくは　.jsonで　定義したformatで処理を分けています。

```ruby:app/controllers/comments_controller.rb
    respond_to do |format|
      format.html { redirect_to product_path(params[:product_id])}
      format.json
    end
```
今回はproducts/show.html.hamlのビューファイルで動作をさせたいので、commentsフォルダ内ではhamlは作成しないのですが、
jsonを動かして上げるためにrailsにデフォルトで実装されているjbuilderというテンプレートエンジンを使います。
views/commentsフォルダ配下にcreate.json.jbuilderを作成しましょう。

```ruby:views/comments/create.json.jbuilder
json.text  @comment.text
json.user_id  @comment.user.id
json.user_name  @comment.user.name
json.seller_id  @sellerId
```
jbuilderではコントローラーで生成した変数に格納されたデータを基にJSON形式の文字列データを生成します。

>上記の記載はコントローラーで定義した変数を

```ruby:controller
@comment = Comment.create(comment_params)
@sellerId = @comment.product.user_id
```
>jbuider内でキー名に置き換えて、.jsファイル内でデータを操作することができる様にします

```ruby:jbuilder
json.キー名 "値"
# {"キー名": "値"}

json.text  @comment.text
json.user_id  @comment.user.id
json.user_name  @comment.user.name
json.seller_id  @sellerId
# {.text: @comment.text, .user_id: @comment.user.id,.user_name: @comment.user.name, .seller_id: @sellerId}
```

### 4-2haml解説
ここで今回使うviewファイルを紹介します。
商品確認ページの中で商品に関するコメントのやりとりを想定しているので、productsフォルダのshow.html.hamlを使用します。

```ruby:views/products/show.html.haml
      .showMain__content__topContent__commentBox
        .comments
          コメント一覧
        - if @comments.exists?
          - @comments.each do |c|
            .comments__box
              = link_to c.user.name, user_profiles_path(c.user.id), class: "comments__box__name"
              - if c.user.id === @product.user.id
                .comments__box__name__mark 出品者
              .comments__box__text
                %p
                  = c.text
        - else
          .comments__box
            .comments__box__noComment
              ＊まだコメントはありません。
        - if current_user
          = form_with(model: [@product, @comment], local: true, id: "comment_create") do |f|
            = f.text_area :text, id: :count_comment, placeholder: "コメントする", maxlength: "400", class: "showMain__content__topContent__commentBox__textField", rows: "4"
            .showMain__content__topContent__commentBox__noticeMsg
              相手のことを考え丁寧なコメントを心がけましょう。
              %br
              不快な言葉遣いなどは利用制限や退会処分となることがあります。
            %br
            - if @product.buyer_id.present?
              .showMain__content__topContent__commentBox__submitBtnSold
                売り切れのためコメントできません
            -else
              = button_tag type: 'submit', class: 'showMain__content__topContent__commentBox__submitBtn' do
                %i.fa.fa-comment
                コメントする
        - else
          .showMain__content__topContent__commentBox__noticeMsg
            ＊コメントの投稿には新規登録/ログインが必要です。
```

ビューファイルの実装内容は下記です。
![スクリーンショット 2020-08-19 1 20 33](https://user-images.githubusercontent.com/62044473/90538968-35bd8180-e1ba-11ea-947e-5e0460b73efc.png)
ビューの中で以下の条件分岐をしています。

```ruby:データと同期した際にコメントがあれば表示、なければ非表示
        - if @comments.exists? #コメントがあればtureを返す
         コメント表示
        - else
         ＊まだコメントはありません。
```
```ruby:ログインしていればコメント可能、していなければ不可能
        - if current_user　#ログインしているユーザーを取得できればtureを返す
         コメントボックスを表示させてコメントができるようにする
        - else
         ＊コメントの投稿には新規登録/ログインが必要です。
```
![スクリーンショット 2020-08-19 21 37 35](https://user-images.githubusercontent.com/62044473/90635476-37438400-e264-11ea-9ec0-f565ba6c3e8b.png)


```ruby:購入者のidが商品テーブルに存在していればコメント不可能、いなければ可能
        - if @product.buyer_id.present? #すでに売れていて購入者idがプロダクテテーブルにあればtureを返す
         売り切れのためコメントできません
        - else
         コメントする
```
![スクリーンショット 2020-08-19 21 36 14](https://user-images.githubusercontent.com/62044473/90635368-13803e00-e264-11ea-89df-8fc1d1469c01.png)

これで、まず以下の要件を満たすことができます。
##### コメントがあればコメントを表示
##### 商品が売れていなければコメント可能
##### ユーザー登録をしていなければコメント不可
##### 商品が売れたらコメント不可

### 4-3jsファイル解説
ここからはjQuery、Ajaxについての解説です
まず全貌はこちらとなります。

```ruby:comments.js
  #Comment機能
  $(function(){
    function buildHTML(comment){
      let html = `<div class="comments__box">
                      <a class="comments__box__name" href=/users/profile/${comment.user_id}>${comment.user_name}</a>
                      <div class="comments__box__text">${comment.text}</div>
                    </div>`
      return html;
    }
    function buildOwnerHTML(comment){
      let html = `<div class="comments__box">
                      <a class="comments__box__name" href=/users/profile/${comment.user_id}>${comment.user_name}</a>
                      <div class="comments__box__name__mark">出品者</div>
                      <div class="comments__box__text">${comment.text}</div>
                    </div>`
      return html;
    }
    $('#comment_create').on('submit', function(e){
      e.preventDefault();
      console.log(this)
      let formData = new FormData(this);
      let url = $(this).attr('action')
      $.ajax({
        url: url,
        type: 'POST',
        data: formData,
        dataType: 'json',
        processData: false,
        contentType: false
      })
      .done(function(data){
        if (data.seller_id == data.user_id) {
          let html = buildOwnerHTML(data);
          $('#comment_create').before(html);
        } else {
          let html = buildHTML(data);
          $('#comment_create').before(html);
        }
        $('.comments__box__noComment').remove()
        $('.showMain__content__topContent__commentBox__textField').val('');
        $('.showMain__content__topContent__commentBox__submitBtn').prop('disabled', false);
      })
      .fail(function(){
        alert('error');
      })
    })
  })
```
jsファイルでは先ほどjbuilderで受け取ったキーを使用していきます。

##### jsでのビューの設定
はい！！順を追って説明します。
まずビューですが、ビューについては下記の`buildOwnerHTML`が出品者表示、`buildHTML`が通常表示となる様に設定しています。

```ruby:comments.js
    function buildHTML(comment){
      let html = `<div class="comments__box">
                      <a class="comments__box__name" href=/users/profile/${comment.user_id}>${comment.user_name}</a>
                      <div class="comments__box__text">${comment.text}</div>
                    </div>`
      return html;
    }
    function buildOwnerHTML(comment){
      let html = `<div class="comments__box">
                      <a class="comments__box__name" href=/users/profile/${comment.user_id}>${comment.user_name}</a>
                      <div class="comments__box__name__mark">出品者</div>
                      <div class="comments__box__text">${comment.text}</div>
                    </div>`
      return html;
    }
```
- ビューのHTMLはテンプレートリテラルで記載していますが、プレースホルダー`${}`を用いることでテンプレートリテラルの中での変数の使用が可能になります。

- HTMLの中でコメント内容やユーザーネームが表示される様にjbuilderで設定した`comment.text`や`comment.user_name`をプレースホルダーで囲んでデータを引っ張ってきています。

- また、aタグを用いてhref属性でリンク先を指定することにより、ユーザーネームを押下したらユーザーのプロフィール画面へ飛ぶ様にしています。設定したプロフィール画面のurlは `rails routes`で確認すると、`/users/profile/:id`となっているのですが、この`:id`にjbuilderで設定した、`comment.user_id`をプレースホルダーで囲んで、`${comment.user_id}`としてあげることで、コメントしたユーザーのプロフィール画面へ飛ぶようにしてあげています。

>この`buildOwnerHTML`(出品者表示)と`buildHTML`(通常表示)は`出品者がコメントしたら、ニックネームの下に出品者と表示`と言う要件を満たすためにあとで使用します。

##### 4-3-2取得データ基の指定
次はおなじみのコードですね。

```ruby:comments.js
$(function(){
  $('#comment_create').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
    let url = $(this).attr('action')
  })
})
```
- `$('#comment_create')`とありますが、`show.html.haml`の中の form_withに `id:"comment_create"`を設定しています。
form_withではフォーム用を生成しますが、このフォームが送信されたら動くプログラムです。

- 送信されたらということなので、 `.on('submit', function(e){})`を使用しています。ちなみにもうご存知だとは思いますが、この`()`の中の`e`は event の`e`です。発生したイベントを`e`で受け渡しています。

- form_withでフォームを送信する際に、デフォルトだと自動的に通信が行われてしまいますが、今回は非同期通信を行いますので、`e.preventDefault();`で自動で起こるイベントを無効化します。

- ここで`FormData();`というものができてますが、`FormData();`はform_withで送信したフォームの情報を取得しています。`FormData(this);`のthisは直前の”メソッド、関数"などを取得できる便利なものですが、ここでは、`'#comment_create'`を取得しています。つまり、`FormData('#comment_create')`となるということですね。

- new FormData(this);のnewは新規でフォームデータを取得するということです。

- その後ろの`let url = $(this).attr('action')`ですが、
まず、$(this)で先ほどのフォームデータの要素をjQueryオブジェクトに変換しています。

- そして、 `attr`ですが、これはよく使用するので、是非覚えておいてください。`attr`は日本語でいうところのアトリビュートですが、HTMLが持つ　idやclassなどの属性を変更、取得、設定することができます。

- この`attr`で、フォームデータの中から、`'action'`を取得しています。この`'action'`をconsole.log(this)で見ると、下記の様にurlを取得していることが理解できます。このurlは`/products/1/comments`となっていますが、ここから、商品のid１に対するコメントだということがわかります。
![スクリーンショット 2020-08-19 2 18 41](https://user-images.githubusercontent.com/62044473/90544614-5558a800-e1c2-11ea-8a8f-0ca27de2bea5.png)

>まとめると、'#comment_create'というidが付与された新しいform_withのフォームの内容をnew FromDataで取得して、`formData`という変数に代入し、`formData`の中のurlを取得しているということです。

##### 4-3-3JSONデータ取得(.ajaxの中についてみていきます)
Ajaxとは"Asynchronous JavaScript + XHR(XMLHttpRequest)"の略です。XHRはJavaScriptなどのウェブブラウザ搭載のスクリプト言語でサーバとのHTTP通信を行うための、組み込みオブジェクト（API）です。Google Mapとかが使っている非同期で地図を確認できる技術ですね！！

細かく解説していきます。
まず、コードです。

```ruby:comments.js
      $.ajax({
        url: url,
        type: 'POST',
        data: formData,
        dataType: 'json',
        processData: false,
        contentType: false
      })
```

- `url:`これはリクエストが送信されるURLです。今回は`attr`で取得した`url`を指定しています。
- `type:`これはRailsで言うところのHTTPリクエストメソッド（'POST','GET'等)です。今回は`'POST'`を指定しています。
- `data:`これはサーバーへ送信するデータです。今回は`formData`の値を指定しており、上記で説明したXHRの機能を果たしています。
- `dataType:`これはサーバーから返されるデータの型です。今回は`'json'`を指定しています。
- `processData:`dataに指定している対象を文字列に変換するかを指定できるものです。初期値は```true```になっており、文字列に自動変換されてエラーとなります。今回は`false`として文字列となるのを防ぎます。FormDataでデータ取得時に記載すると覚えておきましょう。
- `contentType:`これはサーバーへデータ送信を行う際のコンテンツタイプです。FormDataでデータ取得時に記載します。デフォルトでは"application/x-www-form-urlencoded; charset=UTF-8"が指定されており、ほとんどの場合、特に変更する必要はありませんが、コンテンツタイプを変更したいときは指定します。`FormData`でのデータ取得時は必ず`false`とするで問題ありません。

ちなみに、以下のコンソールを見るとjbuilderで指定されたJSONデータが送信されていることがわかりやすく理解できますね。
![スクリーンショット 2020-08-19 19 38 24](https://user-images.githubusercontent.com/62044473/90624851-98169080-e253-11ea-8f27-ac87c4db3607.png)

ここからはこのコンソールで表示されているデータを頭に置いて解説を聞いていただけるとよりわかりやすいと思います。
##### 4-3-4取得データの処理(.done.failの中についてみていきます)
以下では.ajaxで完了した処理が成功か失敗かで処理を分岐しています。

```ruby:comments.js
       .done(function(data){
        if (data.seller_id == data.user_id) {
          let html = buildOwnerHTML(data);
          $('#comment_create').before(html);
        } else {
          let html = buildHTML(data);
          $('#comment_create').before(html);
        }
        $('.comments__box__noComment').remove()
        $('.showMain__content__topContent__commentBox__textField').val('');
        $('.showMain__content__topContent__commentBox__submitBtn').prop('disabled', false);
      })
      .fail(function(){
        alert('error');
      })
```
まず、シンブルな`.fail`の中身について説明します。
`.fail`では処理が失敗したときに`error`と言うエラーメッセ時を`aleart`を使って表示させています。

次に`.done`についてです。
`.done`の中では非同期通信に成功した場合の処理が走ります。非同期通信に成功したら、一番はじめに返される値は`create.json.juilder`で指定したデータです。そのデータは上で定義している`data:`に入っているので、`function(data)`としています。

指定したい要件に

- 出品者がコメントしたら、ニックネームの下に出品者と表示

と言うものがあるのですが、この要件を満たすために`.done`のなかで表示するビューを変更する`if`文を書いています。
出品者を判断するメソッドはjbuilderの中で

```
data.seller_id @sellerId
```
としています。

この`data.seller_id`はコントローラーで指定した`@sellerId`が入っていますが、`@sellerId`を紐解くと

```
@comment = Comment.create(comment_params)
@sellerId = @comment.product.user_id
```
と出品されている商品のuser_id（productテーブルのuser_id)を紐づいけています。
この`data.seller_id`と現在コメントしているユーザーの`id`つまり、ログインしているユーザーの`id`が一致しているかを条件として分岐します。

ログインしているユーザーの`id`は、コントローラーの`comment_params`で取得した`user_id: current_user.id`を引っ張って、`@comment`に代入し、jbuilderで

```
json.user_id  @comment.user.id
```
としていることから、if文の中では下記の様に条件を設定します。

```ruby:if文
if (data.seller_id == data.user_id)
```
これで`true`を返せば出品者と表示、`false`を返せば出品者を非表示とします。

```ruby:if文
        if (data.seller_id == data.user_id) {
          let html = buildOwnerHTML(data);
          $('#comment_create').before(html);
        } else {
          let html = buildHTML(data);
          $('#comment_create').before(html);
        }
```

- `true`の場合は上で定義した`buildOwnerHTML`(出品者表示)に、`false`の場合は`buildHTML`(通常表示)に`data`を渡してhtml変数とします。
これで

##### 出品者がコメントしたらニックネームの下に出品者と表示
と言う要件を満たしています。
<img width="570" alt="スクリーンショット 2020-08-19 20 06 36" src="https://user-images.githubusercontent.com/62044473/90627904-a23a8e00-e257-11ea-923f-1f1c2ab32391.png">

その後は`$('#comment_create').before(html)`となっていますが、ここで、`show.html.haml`にどの様な記載があるのか確かめてみましょう。

# 5要素の操作
![スクリーンショット 2020-08-19 18 32 09](https://user-images.githubusercontent.com/62044473/90617982-51706880-e24a-11ea-9e7b-65e1649bd37a.png)

上のスクショに`id="comment_create"`が青く網掛けされているのが見えると思いますが、非同期でHTMLを表示させる際に、この`'#comment_create'`と言う要素の前・後どちらに表示するかを操作します。

### 5-1beforeについて

```ruby:comments.js
   $('#comment_create').before(html);
```
`.before()`は`()`の中に指定した要素やテキストをclassやidの前に追加します。
今回は、`$('#comment_create').before(html)`となっていますが、`$('#comment_create')`と言うidの前に、`html`(buildOwnerHTML(data) もしくは　buildHTML(data))を追加して表示させます。
これで非同期通信で、必ずtest_areaの上にコメントが表示されるようになります。
そうすることで
<a id="anchor30"></a>
##### コメントを投稿して非同期通信で表示させる際にコメントを上から古い順番に並べる
と言う要件を満たします。

### 5-2appendについて

```ruby:comments.js
   $('#comment_create').append(html);
```
`.append()`は`()`の中に指定した要素やテキストをclassやidの後ろに追加します。
もし、$('#comment_create')`と言うidの後ろに、`html`(buildOwnerHTML(data) もしくは　buildHTML(data))を追加して表示させたい場合は、`$('#comment_create').append(html)`とすることで実現できると言うわけです。
### 5-3removeについて
```ruby:comments.js
   $('.comments__box__noComment').remove()
```
`.remove()`は指定した要素をその配下の子要素ごと削除するメソッドです。
以下をご覧ください。
<img width="734" alt="スクリーンショット 2020-08-19 19 58 20" src="https://user-images.githubusercontent.com/62044473/90626813-5affcd80-e256-11ea-8795-e227f953491f.png">
今回は、デフォルトでコメントがなければ `＊まだコメントはありません。` と表示させています。
非同期通信でコメントがついた段階で、 `＊まだコメントはありません。` と言う表示を消すために`.done`の処理の中で`$('.comments__box__noComment')`と言う要素に対して`.remove()`を使用することで
<a id="anchor40"></a>
##### コメントがなければまだコメントはありませんと表示
と言う要件を満たしています。
### 5-4valについて
```ruby:comments.js
   $('.showMain__content__topContent__commentBox__textField').val('');
```
`.val()`はHTMLのvalue値を設定・変更・取得することができるメソッドです。
下記をご覧ください。
![スクリーンショット 2020-08-19 19 48 59](https://user-images.githubusercontent.com/62044473/90626393-e167df80-e255-11ea-95ff-6e50f5f1dbc6.png)

このコメントのtext_areaは`.showMain__content__topContent__commentBox__textField`と言うクラスなのですが、`.val('')`を設定することによって上のスクショの`はい`と言う表示が非同期の状態でも表示されず
<a id="anchor50"></a>
##### 非同期の状態でも連続してコメントを入力したい
と言う要件を満たすことができます。
### 5-5propについて
```ruby:comments.js
   $('.showMain__content__topContent__commentBox__submitBtn').prop('disabled', false);
```
`.prop()`は、property(プロパティ)と言う意味で、`()`の中にプロパティを入れます、例えばチェックボックスにチェックを入れたいときに`.prop('checked')`とすることでチェックを入れて表示を操作できたりします。
今回は送信ボタンを押下すると、プロパティが`'disabled'`になり、非同期で再度送信ボタンを押せなくなってしまうのを防ぐために`.prop('disabled', false)`としています。
そうすることでボタンを押下してもボタンのプロパティが`'disabled'`にならず
<a id="anchor60"></a>
##### 非同期の状態でも連続して送信ボタンを押したい
と言う要件を満たしています。
本記事で出てきたprop()とattr()はとても便利なのですが、こちらの記事がとてもわかりやすかったので参考までに。
https://agohack.com/attr-prop-attributes/

#6まとめ
web開発はいろいろなやり方があり、奥が深いので駆け出しの方はこれを一つのやり方として、他にもいろいろ試してくださいね。勉強中なので、もっとこうした方が良いなどのコメントは是非どんどんください。
##### 要件へのリンク
[コメントがあればコメントを表示](#コメントがあればコメントを表示)
[商品が売れていなければコメント可能](#商品が売れていなければコメント可能)
[ユーザー登録をしていなければコメント不可](#ユーザー登録をしていなければコメント不可)
[商品が売れたらコメント不可](#商品が売れたらコメント不可)
[出品者がコメントしたら、ニックネームの下に出品者と表示](#出品者がコメントしたらニックネームの下に出品者と表示)
[コメントを投稿して非同期通信で表示させる際にコメントを上から古い順番に並べる](#コメントを投稿して非同期通信で表示させる際にコメントを上から古い順番に並べる)
[コメントがなければ` ＊まだコメントはありません。`と表示](#コメントがなければまだコメントはありませんと表示)
[非同期の状態でも連続してコメントを入力したい](#非同期の状態でも連続してコメントを入力したい)
[非同期の状態でも連続して送信ボタンを押したい](#非同期の状態でも連続して送信ボタンを押したい)

>##### 参考にした記事
>jbuilder
https://github.com/rails/jbuilder

>ajax
http://js.studio-kingdom.com/jquery/ajax/ajax
https://ja.wikipedia.org/wiki/XMLHttpRequest
https://www.ryotaku.com/entry/2019/01/16/224131
https://qiita.com/tanutanu/items/239abfe88bbbeec772bf

>preventDefault
https://developer.mozilla.org/ja/docs/Web/API/Event/preventDefault

>this
https://qiita.com/takeharu/items/9935ce476a17d6258e27

>aleart
https://techacademy.jp/magazine/5486

>val
https://www.sejuku.net/blog/45297

>prop
https://agohack.com/attr-prop-attributes/