RailsとAjaxを使ったいいね機能の非同期通信

転職活動用に個人アプリを開発中です。
今回、RailsとAjaxを使って、いいね機能の非同期化を行いました。

<u>AjaxではjQueryを使うため、jQueryを使えるようにしておく事前準備が必要です。それは[参考記事](https://qiita.com/__tambo__/items/45211df065e0c037d032)を見てください。以下の記述はそれが設定済みのうえでの話です。</u>

![PFCMASTERいいね機能Ajax.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547640/5d1432fd-7fc2-5870-92db-031410b5f21e.png)

### 実現した機能
・「いいね」ボタンを押すとリロードせずに「いいねを取り消す」に表示が変わる
・「いいね」ボタンを押すとリロードせずにlikesテーブルにデータが1つ追加される
・「いいね」ボタンを押すとリロードせずにいいね数が1つ増える
※その逆もしかり

# このコードでうまくいきました

### コントローラー(likes_controller.rb)

```ruby:likes_controller.rb
class LikesController < ApplicationController

  def create
    @post = Post.find(params[:post_id])
    @like = current_user.likes.build(post_id: params[:post_id])
    @like.save
    @likeCounts = Like.where(post_id: params[:post_id])
  end

  def destroy
    @post = Post.find(params[:post_id])
    @like = Like.find_by(post_id: params[:post_id], user_id: current_user.id)
    @like.destroy
    @likeCounts = Like.where(post_id: params[:post_id])
  end

end
```

### 個別の投稿ページ(上記の画像のページ)

```ruby:show.html.haml
.like
  = render partial: "likes/like", locals: {post: @post}
```

###部分テンプレート(_like.html.haml)

```ruby:_like.html.haml
- if user_signed_in?
  - if current_user.already_liked?(post)
    = button_to 'いいねを取り消す', post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true
  - else
    = button_to 'いいね', post_likes_path(post.id), method: :post, remote: true
.likeCounts
  いいね数：
  = post.likes.count
```



### 更新したい部分のビュー(いいねしたとき)
```ruby:create.js.erb
$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");
```

### 更新したい部分のビュー(いいねを取り消すとき)
```ruby:destroy.js.erb
$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");
```

# JavaScriptが動く仕組み
・`link_to`や`button_to`には:remoteオプション(`remote: true`)がある。`button_to`に`remote: true`を追加することで、js形式のリクエストを送信できるようになる。

```ruby
= button_to 'いいね', post_likes_path(post.id), method: :post, remote: true
```

参考：[Railsガイド](https://railsguides.jp/working_with_javascript_in_rails.html)


# 非同期通信の流れ
1.「いいね」ボタンを押す
2.下記のリンクで`likes#create`にjs形式でリクエストが飛ばされる

```ruby
= button_to 'いいね', post_likes_path(post.id), method: :post, remote: true
```
3.likesコントローラーのcreateアクションが動き、いいねが保存される。Likeモデルを介してデータベースに追加される(リロードせずに)
4.更新したい部分のページ`create.js.erb`,`destroy.js.erb`がレスポンスとして返される。`.html`(jQueryのhtmlメソッド)は、`.like`(likeクラス)の部分をhtmlの後ろの(  )内に置き換える役割をはたす。

### 今回苦労したところ
1.部分テンプレートの理解

```ruby:create.js.erb
$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");
```
partialオプション：部分テンプレートの呼び出しを行う。今回はlikesフォルダの_like.html.hamlを呼び出したいので`likes/like`となる。

localsオプション：部分テンプレート内で{ }内の左辺が変数として使えるようになる。今回でいうと`post`が変数として使えるようになる。右辺の`@post`は何かというと、右辺の`@post`が左辺の`post`に代入して、それが変数として使えるようになる。右辺の`@post`はどこからきているかというと、postsコントローラのshowアクションで定義しているので、そこからきている。

```ruby:posts_controller.rb
def show
  @post = Post.find(params[:id])
  Like.new
end
```

2.`_like.html.haml`の`button_to`のpathの設定
ずっとこのエラーに悩まされていました。

```
ActionView::Template::Error No route matches (中略) missing required keys: [:id])
```
[この記事](https://qiita.com/shiro-kuro/items/f017dce3d199f06d1dcd)を見つけてようやく下記のように記述して解決できました。

```ruby
= button_to 'いいねを取り消す', post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true
```

解決はしたものの、この部分`id: post.likes[0].id`がまだちゃんと理解できていません。
1つの投稿に複数のいいねがついていたとして、その最初のいいねのidを取得している？

今考えてみると、確かに1つの投稿に複数のいいねがある場合、「どのいいねを取り消すの？指定してくれないとわからないよ」と言われても無理ないなと思いました。

~~だとすると、必ずしもcurrent_userの付けたいいねではなく、他の人の付けたいいねを取り消してしまう？
まだ修正する必要があるかもしれません😅~~
(追記)
Sequel Proで確認したところ、他の人のいいねを取り消してしまうことはなく、ちゃんとcurrent_userのいいねが取り消されていました。

~~一応いいねを消せることは消せます。
この点が明らかになったらまた追記します。~~


### 参考記事
[Railsで remote: true と js.erbを使って簡単にAjax(非同期通信)を実装しよう！(いいね機能のデモ付)](https://qiita.com/__tambo__/items/45211df065e0c037d032)
[【Rails×Ajax】いいね機能ハンズオン](https://qiita.com/naberina/items/c6b5c8d7756cb882fb20)
[Railsでいいね機能を実装。Ajaxを使い非同期対応。で](https://qiita.com/shiro-kuro/items/f017dce3d199f06d1dcd)