FreeCamp

Ajaxを用いて非同期いいねを実装

SNSでは画面がリロードしていないのに関わらず、画面の一部が変わっていることはありませんか?
いいねのハートの色が変わっていたことなどありませんか?
それはAjaxという技術を用いて、画面の一部だけ通信を行い、JSで画面の表示を変えているのです。
今回はそのAjaxを用いて非同期通信を行い、いいねのハートの色といいね数を画面のリロードなしで変更するコードを書いていきます。

見出し

  1. 同期通信のいいね機能
  2. 投稿を部分テンプレート化
  3. 非同期通信のいいね機能
  4. 完成ソースコード

1. 同期通信のいいね機能

まずは以下記事を参考にして、いいねした時に画面遷移が行われるアプリケーションを作成しましょう。

また、以下GitHubリポジトリから完成ソースコードを取得することも可能です。

https://github.com/ssshhhooota/rails_favorite_count

$ git clone git@github.com:ssshhhooota/rails_favorite_count.git
$ cd rails_favorite_count
$ bundle
$ rails db:migrate

ここまでできたら一度アプリケーションを立ち上げて確認してみましょう。
同期通信で動作するいいね機能が実装できていると思います。

2. 投稿を部分テンプレート化

非同期通信は画面の一部からサーバーにリクエストを送り、
サーバー側で何らかの処理を行い、フロントにレスポンスを送ります。
その流れの中で、通信するデータ容量が多いと非同期通信とはいえ、画面の一部を変更するのにも時間がかかります。(いいねのハートの色を変える等)
ですので、極力通信するデータの容量を少なくするのも工夫の一つです。
リクエストはいいねする対象のID、レスポンスは投稿一つの部分テンプレートとします。

...編集
<% @posts.each do |post| %>
  <tr>
    <%= render 'post', post: post %>
  </tr>
<% end %>
<td><%= post.title %></td>
<% if current_user.favorited_by?(post.id) %>
  <td><%= link_to 'いいね外す', destroy_favorite_path(post), method: :DELETE %> <%= post.favorites.count %></td>
<% else %>
  <td><%= link_to 'いいねする', create_favorite_path(post), method: :POST %> <%= post.favorites.count %></td>
<% end %>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>

_post.html.erbを作成し、投稿一つを表示する部分テンプレートにしています。
その部分テンプレートをeach文の中で呼び出すことによって、全ての投稿が表示されます。

3. 非同期通信のいいね機能

それでは肝心な非同期通信を実装していきます。
まずはlink_toにremote: trueオプションをつけます。

...編集
<% if current_user.favorited_by?(post.id) %>
  <td><%= link_to 'いいね外す', destroy_favorite_path(post), method: :DELETE, remote: true %> <%= post.favorites.count %></td>
<% else %>
  <td><%= link_to 'いいねする', create_favorite_path(post), method: :POST, remote: true %> <%= post.favorites.count %></td>
<% end %>

jQueryが使用できるように設定します。

...追加
gem 'jquery-rails'
$ bundle
...編集
//= require rails-ujs
//= require activestorage
//= require jquery
//= require turbolinks
//= require_tree .

次に、favorites_controller.rbのcreateアクションとdestroyアクションのredirect_toを削除します。
redirect_toを指定してしまうと画面遷移が行われてしまい、非同期通信が行われなくなってしまうためです。

class FavoritesController < ApplicationController
  before_action :post_params
  def create
    @favorite = Favorite.create(user_id: current_user.id, post_id: @post.id)
  end

  def destroy
    @favorite = Favorite.find_by(user_id: current_user.id, post_id: @post.id).destroy
    redirect_to posts_path
  end

  private

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

先ほど、レスポンスは投稿一つの部分テンプレートとしましたが、実際にはサーバーからは何も返しません。
今回のいいね処理ではすでにある部分テンプレートを切り替えるだけなので、何も返さないが、表示は切り替えます。
切り替えるのにjsを使います。

これは覚えて欲しい内容ですが、
favorites_controller.rbのcreateアクションが呼ばれた時はviews/favorites/create.js.erbが呼ばれます。
他のコントローラの時も同様で、
posts_controller.rbのindexアクションが呼ばれた時は
views/posts/index.js.erbが呼ばれます。
※ファイルがあれば呼び出されます。

js.erbはhtml.erbと概念は同じです。
html.erbはhtmlの中に<% %>や<%= %>を記述できました。
js.erbはjsの中に<% %>や<%= %>を記述することが可能です。

それではviews/favorites/create.js.erbとviews/favorites/destroy.js.erbを作成していきます。

alert('createアクション');
alert('destroyアクション');

alert()と記述したのは一種のデバック方法です。
いいねした後にalert(‘createアクション’);が発火するかどうか確かめるかつ、jsが発火しているか確かめるために記述しました。
アプリケーションを実行し、試してください。
アラートダイアログが表示されたことを確認できたらファイルの中身を書き換えましょう。

と、その前に部分テンプレートを切り替えるのですが、
投稿がたくさんある中で「どこの」部分テンプレートを切り替えるのでしょうか?
アプリケーションはそこまで自動で行ってくれません。
なので、切り替えるための指標が必要となります。
今回はeach文の中のtrタグに投稿それぞれ異なるクラスを付与して判別できるようにしましょう。

<% @posts.each do |post| %>
      <tr class="post_<%= post.id %>">
        <%= render 'post', post: post %>
      </tr>
    <% end %>

class=”favorite_<%= post.id %>” ←この部分で投稿それぞれのtrクラスに一位な値を付与しています。

それではcreate.js.erbとdestroy.js.erbを編集していきます。

$(".post_<%= @post.id %>").html("<%= j(render 'posts/post',  post: @post ) %>")
$(".post_<%= @post.id %>").html("<%= j(render 'posts/post', post: @post ) %>");

これでAjaxを用いた非同期いいねを実装することができました。

4. 完成ソースコード

https://github.com/ssshhhooota/rails_favorite_ajax