【Ajax】RailsでコメントのCRUD機能を爆速で作る【非同期】

# コメント機能(Ajax)のモッグを爆速で作成したい時のためにメモ

## rails g コマンドで投稿とコメントを自動生成

#### アプリ新規作成

```
$ rails _5.2.4_ new sample-comment -d postgresql --skip-bundle && cd sample-comment
```

#### 余分なファイルが作られないように設定

```config/application.rb
module SampleComment
  class Application < Rails::Application
    # ここから下を追加(scaffold対策)
    config.generators do |g|
      g.javascripts false
      g.helper false
      g.test_framework false
    end
  end
end
```

#### 投稿とコメントのCRUDを自動生成

```
$ rails g scaffold post title:string body:string && rails g scaffold comment content:string post:references && rails db:migrate && rails s
```

#### 使わないファイル削除して、新規にjs.erbなどを作成

```
$ cd ./app/views/comments && rm -r * && touch _edit.html.erb && touch _edit_error.html.erb && touch _error.html.erb && touch _form.html.erb && touch _index.html.erb && touch destroy.js.erb && touch edit.js.erb && touch index.js.erb && touch update.js.erb && cd ../../..
```


## jQueryの導入

#### gemのインストール

```ruby:Gemfile
gem 'jquery-rails'
```

#### application.jsにjqueryを追記

```app/assets/javascripts/application.js
//
//= require jquery        #追記
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require_tree .
```


#### bundle install & DB作成

```
$ bundle && rails db:create
```


## アソシエーション

```post.rb
class Post < ApplicationRecord
  has_many :comments, dependent: :destroy
end
```

```comment.rb
class Comment < ApplicationRecord
  belongs_to :post, optional: true
end
```

## ルーティング

```routes.rb
Rails.application.routes.draw do
  resources :posts do
    resources :comments, only: [:create, :destroy, :edit, :update]
  end
end
```

## View

**投稿の詳細画面**

```rb:views/posts/show.html.erb
<p id="notice"><%= notice %></p>
<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>
<p>
  <strong>Body:</strong>
  <%= @post.body %>
</p>
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
<div id="comments_area">
  <%= render partial: 'comments/index', locals: { comments: @comments, post: @post } %>
</div>
  <%= render partial: 'comments/form', locals: { comment: @comment, post: @post } %>
```

**コメントの編集**

```rb:views/comments/_edit.html.erb
<%= render 'layouts/flash' %>
<div id="comment_edit_form">
  <%= form_with(model: [post, comment]) do |f| %>
    <div class="input-field">
      <%= f.text_area :content, class: "materialize-textarea", id: "comment_content_#{post.id}" %>
      <%= f.label :content, class: "active" %>
      <%= button_tag :type => "submit", :class =>"btn waves-effect waves-light" do %>
        送信
      <% end %>
      <%= link_to :back, class: "btn white black-text waves-effect waves-light" do %>
        戻る
      <% end %>
    </div>
  <% end %>
</div>
```

**コメント編集のエラーメッセージ**

```rb:views/comments/_edit_error.html.erb
<% if @comment.errors.any? %>
  <div id="error_explanation">
    <%= render 'layouts/flash' %>
    <h5><%= @comment.errors.count %>件のエラーがあります。</h5>
    <ul>
      <% @comment.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>
```

**コメントのエラーメッセージ**

```rb:views/comments/_error.html.erb
<% if @comment.errors.any? %>
  <div id="error_explanation">
    <%= render 'layouts/flash' %>
    <h5><%= @comment.errors.count %>件のエラーがあります。</h5>
    <ul>
      <% @comment.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>
```

**コメントの新規作成**

```rb:views/comments/_form.html.erb
  <div class="comment-form">
    <%= form_with(model: [post, comment]) do |f| %>
      <div class="input-field">
        <%= f.text_area :content, class: "materialize-textarea" %>
        <%= f.label :content, "コメント内容", class: "active" %>
        <span class="helper-text" data-error="wrong" data-success="right">1から140文字以内</span>
      </div>
      <div class="comment-button">
        <%= button_tag :type => "submit", :class =>"waves-effect waves-light btn" do %>
          <%= t('.comment') %>
        <% end %>
      </div>
    <% end %>
  </div>
```

**コメント一覧**

```rb:views/comments/_index.html.erb
<%= render 'layouts/flash' %>
<div class="post-content show-box">
  <%= simple_format(post.title) %>
</div>
<% comments.order(created_at: :desc).each do |comment| %>
  <% if comment.id.present? %>
    <div class="show-comment show-box" id="comment_<%= comment.id %>">
      <div>
      </div>
      <div id="errors_area_<%= comment.id %>">
        <%= render partial: 'comments/edit_error' %>
      </div>
      <div class="comment-content" id ="this_comment_<%= comment.id %>">
        <%= simple_format(comment.content) %>
      </div>
      <span>投稿日時:<%= time_ago_in_words(comment.created_at) %></span>
        <span class="badge"><%= link_to t('.delete'), post_comment_path(comment.post_id, comment.id), method: :delete, remote: true,
                                        data: { confirm: "本当に削除しますか?"} %></span>
        <span class="badge"><%= link_to t('.edit'), edit_post_comment_path(post, comment), remote: true %></span>

    </div>
  <% end %>
<% end %>
```

**コメントの削除js**

```rb:views/comments/destroy.js.erb
$('#comment_<%= @comment.id %>').remove();
```

**コメントの編集js**

```rb:views/comments/edit.js.erb
$("#this_comment_<%= @comment.id %>").replaceWith("<%= j render 'comments/edit', post: @post, comment: @comment %>");
```

**コメントの一覧js**

```rb:views/comments/index.js.erb
$("#comments_area").html("<%= j(render 'comments/index', { comments: @comment.post.comments, post: @comment.post }) %>")
$("textarea").val('')
$('#error_explanation').remove();
```

**コメントのアップデートjs**

```rb:views/comments/update.js.erb
$('#comments_area').replaceWith
("<%=j render 'comments/index',post: @post, comment: @comment, comments: @post.comments, remote: true %>");
```

## フラッシュメッセージの部分テンプレート

**ターミナルで_flash.html.erbファイルを作成**

```
$ cd touch app/views/layouts/_flash.html.erb
```

**_flash.html.erbファイルを編集**

```rb:views/layouts/_flash.html.erb
<% flash.each do |message_type, message| %>
    <div class="alert alert-<%= message_type %>">
      <%= message %>
    </div>
<% end %>
```

## controller

**コメントコントローラー**

```rb:comments_controller.rb
class CommentsController < ApplicationController
  before_action :set_post, only: [:create, :edit, :update, :destroy]

  def create
    @comment = @post.comments.build(comment_params)
    respond_to do |format|
      if @comment.save
        flash.now[:notice] = 'コメントが投稿されました'
        format.js { render :index }
      else
        flash.now[:notice] = 'コメントの投稿に失敗しました'
        format.js { render :error }
      end
    end
  end

  def edit
    @comment = @post.comments.find(params[:id])
    respond_to do |format|
      flash.now[:notice] = 'コメントの編集中'
      format.js { render :edit }
    end
  end

  def update
    @comment = @post.comments.find(params[:id])
      respond_to do |format|
        if @comment.update(comment_params)
          flash.now[:notice] = 'コメントが編集されました'
          format.js { render :index }
        else
          flash.now[:notice] = 'コメントの編集に失敗しました'
          format.js { render :edit_error }
        end
      end
  end

  def destroy
    @comment = Comment.find(params[:id])
      @comment.destroy
      respond_to do |format|
        flash.now[:notice] = 'コメントが削除されました'
        format.js { render :index }
      end
  end

  private

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

  def set_post
    @post = Post.find(params[:post_id])
  end
end
```

**投稿コントローラー**

```posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  def index
    @posts = Post.all.order(created_at: :desc)
  end

  def show
    @comments = @post.comments
    @comment = @post.comments.build
  end

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to posts_path
    else
      render 'new'
    end
  end

  def edit
  end

  def update
    if @post.update(post_params)
      redirect_to posts_path
    else
      render 'edit'
    end
  end

  def destroy
    @post.destroy
    redirect_to posts_path
  end

  private

  def post_params
    params.require(:post).permit(:title, :body)
  end

  def set_post
    @post = Post.find(params[:id])
  end

end
```

以上です！

バリデーション エラーの表示はこちら
https://qiita.com/38lvd3/items/309110c678592e5ecbc7

参考
https://qiita.com/nojinoji/items/2034764897c6e91ef982
https://qiita.com/yuto_1014/items/c7d6213139a48833e21a
https://qiita.com/hiro266/items/56ec2c350fd9d8ca22d8
https://qiita.com/hiro266/items/160a4f9290ecd9f27d15