【Rails】Ajaxを用いた非同期投稿・削除の実装

# 目標

![ezgif.com-video-to-gif (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/0acf5ccf-1042-3f4c-1f7a-d3d819f5feb5.gif)

# 開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

# 前提

下記実装済み。

・[Slim導入](https://qiita.com/matsubishi5/items/b639ffddecc626856039)
・[Bootstrap3導入](https://qiita.com/matsubishi5/items/b117c95f4dd71340cc80)
・[ログイン機能実装](https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d)

# 投稿機能を実装

### 1.モデル

```terminal:ターミナル
$ rails g model Book user_id:integer title:string body:text
```

```terminal:ターミナル
$ rails db:migrate
```

```ruby:schema.rb
ActiveRecord::Schema.define(version: 2020_04_05_115005) do
  create_table "books", force: :cascade do |t|
    t.integer "user_id"
    t.string "title"
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end
```

```ruby:user.rb
class User < ApplicationRecord
  has_many :books, dependent: :destroy
end
```

```ruby:book.rb
class Book < ApplicationRecord
  belongs_to :user
end
```

### 2.コントローラー

```terminal:ターミナル
$ rails g controller books index
```

```books_controll.rb
class BooksController < ApplicationController
  def create
    @book = Book.new(book_params)
    @book.user_id = current_user.id
    if @book.save
      redirect_to book
    else
      @books = Book.all
      render 'index'
    end
  end

  def index
    @book = Book.new
    @books = Book.all
  end

  def destroy
    @books.destroy
    redirect_to books_path
  end

  private

    def book_params
      params.require(:book).permit(:title, :body)
    end
end
```

### 3.ルーティング

```ruby:routes.rb
Rails.application.routes.draw do
  resources :books
end
```

### 4.ビュー
```slim:books/index.html.slim
.row
  .col-xs-3
    = form_with model: @book, local: true do |f|

      = f.label :title, 'タイトル'
      br
      = f.text_field :title, class:'form-control'
      br

      = f.label :body, '本文'
      br
      = f.text_area :body, class:'form-control'
      br

      = f.submit '投稿', class: 'btn btn-primary btn-block'

  .col-xs-9
    table.table
      thead
        tr
          th
            | 投稿者
          th
            | タイトル
          th
            | 本文
          th

      tbody.new_book
        - @books.each do |book|
          tr
            td
              = link_to book.user  do
                = book.user.name
            td
              = link_to book.title, book_path(book)
            td
              = book.body
            td
              -if book.user == current_user
                = link_to '削除', book, method: :delete, data: { confirm: '本当に削除してもよろしいですか？' }, class: 'btn-sm btn-danger'
```

# 非同期機能を実装

### 1.jQueryを導入

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

```terminal:ターミナル
$ bundle
```

```js:application.js
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery // turbolinksより下に記述
//= require_tree .
```

### 2.createアクションを編集

```books.controller.rb
class BooksController < ApplicationController
  def create
    @book = Book.new(book_params)
    @book.user_id = current_user.id
    unless @book.save
      @books = Book.all
      render 'index'
    end
  end
end
```

### 3.ビューを編集

```slim:books/index.html.slim
.row
  .col-xs-3
    / 「local: true」から「remote: true」に変更
    = form_with model: @book, remote: true do |f|

      = f.label :title, 'タイトル'
      br
      / クラスを付与
      = f.text_field :title, class:'form-control book-title'
      br

      = f.label :body, '本文'
      br
      / クラスを付与
      = f.text_area :body, class:'form-control book-body'
      br

      = f.submit '投稿', class: 'btn btn-primary btn-block'

  .col-xs-9
    table.table
      thead
        tr
          th
            | 投稿者
          th
            | タイトル
          th
            | 本文
          th
      / 投稿一覧の親要素にクラスを付与
      tbody.new_book
        - @books.each do |book|
          / 投稿一覧をパーシャル化
          = render 'books', book: book
```

```slim:books/_books.html.slim
/ 各投稿にIDを付与
tr id='book-#{ book.id }'
  td
    = link_to book.user  do
      = book.user.name
  td
    = link_to book.title, book_path(book)
  td
    = book.body
  td
    -if book.user == current_user
      / 「remote: true」を付与
      = link_to '削除', book, method: :delete, data: { confirm: '本当に削除してもよろしいですか？' }, remote: true, class: 'btn-sm btn-danger'
```

`「remote: true」`を付与する事で、JavaScriptファイルを呼び出せる様になる。
※ 非同期投稿を行うときは必ず`「form_with」`を使用する。

### 5.JavaScriptファイルを作成

```js:books/create.js.erb
$(".book-title").val('');
$(".book-body").val('');
$(".new_book").append("<%= j(render 'books', book: @book) %>");
```

`$(".book-title").val('');`
`$(".book-body").val('');`
➡︎ 入力フォームを空にする

`$(".new_book")`
➡︎ 「4」で付けたクラスを指定

`.append("<%= j(render 'books', book: @book) %>");`
➡︎ 既存投稿の後尾に新規投稿を表示
※ 先頭に表示したい場合は「prepend」を使用する。

```js:books/destroy.js.erb
$("#book-<%= @book.id %>").remove();
```

`$("#book-<%= @book.id %>")`
➡︎ 「4」で付与したIDを指定

`.remove();`
➡︎ 表示を消す