Rails6 コメント機能　非同期通信

## はじめに
Railsでコメント機能を実装します。非同期通信なのでJavasprictを使用していきます（jQuery）
自分用なのでかなり端折ってますが、コメント投稿後にリロードなしで表示させたいっ！て方はぜひ参考にしてください。

## 開発環境、前提条件
* AWS Cloud9
* Ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
* Rails 6.1.7.3
* Deviseのインストールが完了していて、ユーザーの新規登録やログインができる状態
* Comments/Controller.rb　作成済み
* Comment.rb 作成済み


## やっていきましょう！
まずはコントローラの記述をします。

```ruby:controllers/comments/controller.rb
class CommentsController < ApplicationController

  def create
    @post = Post.find(params[:post_id])
    @comment = current_user.comments.new(comments_params)
    @comment.post_id = @post.id

    if @comment.save
      @post.create_notification_comment!(current_user, @comment.id)
      @comments = @post.comments.order(created_at: :desc)
    else
     @error_message = "コメントの投稿に失敗しました"
    end
  end

  def destroy
    @comment = Comment.find(params[:id])
    @comment.destroy
  end

  private

  def comments_params
    params.require(:comment).permit(:content, :post_id)
  end
end
```

続いてモデル
```ruby:models/comment.rb
class Comment < ApplicationRecord

  belongs_to :user   #ユーザー
  belongs_to :post   #投稿

  has_many :notifications, dependent: :destroy #通知

  validates :content, length: { in: 1..140 }
   #コメントできるのは1~140文字以内
end
```
 ## ビュー
Javasprictを使用し投稿したコメントのみビューに追加するのでパーシャルは細かく分けます。
ビューの中はそれぞれのアプリケーションによって変更してください。


まずはコメントフォーム
**local: false**とすることで非同期にします。
```ruby:views/posts/_comment_form.html.erb
<%= form_with(model: [@post, Comment.new], local: false) do |f| %>
  <div class="user-comment">
    <% if current_or_guest_user.email == 'guest@example.com' %>
      <div class="form-group d-flex">
        <% if current_user.profile_picture.attached? %>
          <%= image_tag current_user.profile_picture, size: "50x50", class: 'rounded-circle' %>
        <% end %>
        <%= f.text_field :content, placeholder: "ゲストユーザーはコメントできません", class: "form-control-comment" %>
      </div>
    <% else %>
      <div class="user-comment">
        <div class="form-group d-flex">
          <% if current_user.profile_picture.attached? %>
            <%= image_tag current_user.profile_picture, size: "50x50", class: 'rounded-circle' %>
          <% else %>
            <%= image_tag 'default_profile_picture.png', size: "50x50", class: 'img-fluid rounded-circle mr-2' %>
          <% end %>
          <%= f.text_field :content, placeholder: "コメント入力欄", class: "form-control-comment ml-2 mt-1" %>
          <%= f.submit "コメントする", class: "btn btn-custom" %>
        </div>
      </div>
    <% end %>
  </div>
<% end %>
```

続いてコメントを表示させるビュー
```ruby:views/posts/_comment.html.erb
<div class="comment-container ml-3" id="comment_<%= comment.id%>" >
  <div class="d-flex align-items-start">
      <%= image_tag url_for(comment.user.profile_picture), class: 'img-fluid-comment rounded-circle mr2' if comment.user.profile_picture.attached? %>
    <div>
      <p class="comment-header ml-3"><%= link_to comment.user.username, user_path(comment.user) %></p>
      <p class="comment-content ml-3"><%= comment.content %></p>
    </div>
    <% if current_user == comment.user %>
      <div class="ml-auto mt-auto">
        <%= link_to post_comment_path(@post, comment), method: :delete, remote: true,
        data: { confirm: "本当に削除しますか？" }, class: "comment-delete-link btn btn-link" do %>
          <i class="fas fa-trash" style="color: #FF6347;"></i>
        <% end %>
      </div>
    <% end %>
  </div>
</div>

```

上２つで作成したパーシャルをここに持ってきましょう。こうすることで新しく作成されたコメントのみを表示されることができるのでビュー全体を読み込まなくなります。
```ruby:views/posts/_comments.html.erb
<%= render 'comment_form' %>

<div id="comments">
  <% @comments.each do |comment| %>
    <%= render 'comment', comment: comment %>
  <% end %>
</div>


<script>
document.addEventListener('DOMContentLoaded', (event) => {
  let guestActions = document.querySelectorAll('.guest-action');
  guestActions.forEach((element) => {
    element.addEventListener('click', (event) => {
      event.preventDefault();
      alert('ゲストユーザーはこの操作はできません');
    });
  });
});
</script>
```

## Javasprict
**("#comments").length > 0)**　がなぜかコメントアウトされてるように見えますが、実際にはちゃんと記述されています。

**prepend**は新しいコメントを上に表示させるメソッドです。
下に表示させたい場合は、**append**にしてください

```ruby:views/comments.create.js.erb
<% if @error_message.present? %>
  alert("<%= @error_message %>")
<% else %>
  if ($("#comments").length > 0) {
    $("#comments").prepend("<%= j(render('posts/comment', comment: @comment)) %>")
  }
 $('#comment_content').val("");
<% end %>


```

削除機能もつけると思うのでこれも非同期にしちゃいましょう。
```ruby:views/comments.destroy.js.erb
$("#comment_<%= @comment.id %>").remove()
```

## その他Javasprict
```ruby:config/environment/webpack/environment.js
const { environment } = require('@rails/webpacker')
const jquery = require('./plugins/jquery')
const webpack = require('webpack')

environment.plugins.prepend('Provide',
    new webpack.ProvidePlugin({
        $: 'jquery/src/jquery',
        jQuery: 'jquery/src/jquery',
    })
)

environment.plugins.prepend('jquery', jquery)
module.exports = environment
```

```ruby:app/javasprict/packs/application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

import $ from 'jquery';
window.$ = $;
window.jQuery = $;


import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"

import "jquery";
import "popper.js";
import "bootstrap";
import "../stylesheets/application"
import '@fortawesome/fontawesome-free/js/all'

require("packs/post_picture")



// require("@rails/ujs").start()
// require("turbolinks").start()
// require("@rails/activestorage").start()
// require("channels")
// require("jquery")

import './likes'


Rails.start()
Turbolinks.start()
ActiveStorage.start()
```


## ルーティング
```ruby:config/routes.rb
  resources :posts do
    resources :comments, only:[:create, :destroy]
  end
```


以上で実装の完了です！

参考になりましたら、いいね、コメントお待ちしてます！🎉