【Rails】Ajaxを用いた非同期いいね機能の実装

# 目標

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/2a0a083d-a139-a5c1-0a4e-d6b0b0ba4f9c.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)
・[Font Awesome導入](https://qiita.com/matsubishi5/items/a4c72f436e64ad106ee8)
・[ログイン機能実装](https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d)
・[投稿機能実装](https://qiita.com/matsubishi5/items/1a5aeab651b5822d1f2c)

# いいね機能の実装

### 1.モデル

```terminal:ターミナル
$ rails g model Like user_id:integer book_id:integer
```

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

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

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

```ruby:book.rb
class Book < ApplicationRecord
  has_many :likes, dependent: :destroy

  # 既にいいねしていれば「true」を返す
  def liked_by?(user)
    likes.where(user_id: user.id).exists?
  end
end
```

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

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

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

```likes.controll.rb
class LikesController < ApplicationControllerdef 
  def create
    @book = Book.find(params[:book_id])
    #いいねボタンを連打しても1回しかいいね出来ない様に条件付与
    unless @book.liked_by?(current_user)
      like = current_user.likes.new(book_id: @book.id)
      like.save
      redirect_to @book
    end
  end
    
  def destroy
    @book = Book.find(params[:book_id])
    like = current_user.likes.find_by(book_id: @book.id)
    like.destroy
    redirect_to @book
  end
end
```

### 3.ルーティング

```ruby:routes.rb
Rails.application.routes.draw do
  resources :books do
    resource :likes, only: [:create, :destroy]
  end
end
```

### ビュー

**3.`いいねボタン`をパーシャル化**

```slim:books/show.html.slim
tbody
  - @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
        = render 'like-button', book: book
      td
        -if book.user == current_user
          = link_to '削除', book, method: :delete, data: { confirm: '本当に削除してもよろしいですか？' }, remote: true, class: 'btn-sm btn-danger'
```

**4.`いいねボタン`のパーシャルを作成**

```slim:books/_like-button.html.slim
/ いいねしているかによって色を変える
- if book.liked_by?(current_user)
  = link_to book_likes_path(book), method: :delete do
    i.fas.fa-heart style='color: red;'
    / いいね数
    = book.likes.count

- else
  = link_to book_likes_path(book), method: :post do
    i.far.fa-heart
    = book.likes.count
```

# 非同期機能の実装

### 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.いいね時のジャンプ先を削除

```likes.controller.rb
class LikesController < ApplicationController
  def create
    @book = Book.find(params[:book_id])
    unless @book.liked_by?(current_user)
      like = current_user.likes.new(book_id: @book.id)
      like.save
      redirect_to @book # 削除
    end
  end
    
  def destroy
    @book = Book.find(params[:book_id])
    like = current_user.likes.find_by(book_id: @book.id)
    like.destroy
    redirect_to @book # 削除
  end
end
```

### 3.いいねボタンにIDを付与

```slim:books/index.html.slim
td id='like-button_#{ book.id }'
  = render 'like-button', book: book
```

### 4.いいねボタンに「remote: true」を付与



```slim:books/_like-button.html.slim
- if book.liked_by?(current_user)
  = link_to book_likes_path(book), method: :delete, remote: true do
    i.fas.fa-heart style='color: red;'
    = book.likes.count

- else
  = link_to book_likes_path(book), method: :post, remote: true do
    i.far.fa-heart
    = book.likes.count
```

`「remote: true」`を付与する事で、JavaScriptファイルを呼び出せる様になる。

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

```js:likes/create.js.erb
$("#like-button_<%= @book.id %>").html("<%= j(render 'books/like-button', book: @book ) %>");
```

```js:likes/destroy.js.erb
$("#like-button_<%= @book.id %>").html("<%= j(render 'books/like-button', book: @book ) %>");
```

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

`.html("<%= j(render 'books/like-button', book: @book ) %>");`
➡︎ いいねボタンのパーシャルをrender