💘

【Rails】いいね機能の非同期通信化

2023/07/03に公開

いいね機能を非同期通信を用いて実装します。

Viewファイルの作成

部分テンプレートファイルの作成、非同期通信する要素にID属性を追加します。

部分テンプレートファイル作成

app/views/favorites/_btn.html.erb
<% if book.favorited_by?(current_user) %>
  <%= link_to book_favorites_path(book), method: :delete, remote: true do %>
    <i class="fas fa-heart" aria-hidden="true" style="color: red;"></i>
    <%= book.favorites.count %> いいね
  <% end %>
<% else %>
  <%= link_to book_favorites_path(book), method: :post, remote: true do %>
    <i class="fas fa-heart" aria-hidden="true"></i>
    <%= book.favorites.count %> いいね
  <% end %>
<% end %>

解説:
remote: trueは非同期通信を行うためのオプションです。Railsではデフォルトで非同期通信を処理するためのライブラリが組み込まれているため、実装することによりページがリロードされることなく変更を反映することができます。

idを追加

  • 一覧ページ
app/views/books/_index.html.erb
 <table class="table table-hover table-inverse">
    <thread>
      <tr>
        <th></th>
        <th>Title</th>
        <th>Opinion</th>
        <th colspan="3"></th>
      </tr>
    </thread>
    <tbody>
      <% books.each do |book| %>
      <tr>
        <td><%= link_to book.user do %>
            <%= image_tag book.user.get_profile_image, size:'50x50' %>
            <% end %>
        </td>
        <td><%= link_to book.title,book %></td>
        <td><%= book.body %></td>
+       <td id="favorite_btn_<%= book.id %>">
          <%= render "favorites/btn", book: book %>
        </td>
        <td>コメント数: <%= book.book_comments.count %></td>
      </tr>
    <% end %>
    </tbody>
  </table>

詳細ページ

app/views/books/show.html.erb
  <div class="container">
    <div class="row">
      <div class="col-md-3">
        <h2>User info</h2>
        <%= render "users/info", user: @user %>
        <h2 class="mt-3">New book</h2>
        <%= render "form", book: @books %>
      </div>
      <div class='col-md-8 offset-md-1'>
        <h2>Book detail</h2>
        <table class="table">
          <tr>
            <td><%= link_to @book.user do %>
                <%= image_tag @book.user.get_profile_image, size:"50x50" %><br>
                <%= @book.user.name %>
                <% end %>
            </td>
            <td><%= link_to @book.title, @book %></td>
            <td><%= @book.body %></td>
+           <td id="favorite_btn_<%= @book.id %>">
              <%= render "favorites/btn", book: @book %>
            </td>
            <td>コメント数: <%= @book.book_comments.count %></td>
            
            <% if @book.user == current_user %>
            <td><%= link_to "Edit", edit_book_path(@book), class: "btn btn-sm btn-success" %></td>
            <td><%= link_to "Destroy", @book, method: :delete, data: { confirm: "本当に消しますか?" }, class: "btn btn-sm btn-danger" %></td>
            <% end %>
          </tr>
        </table>
        
        <%= render "book_comments/index", book: @book %>
        <%= render "book_comments/form", book: @book, book_comment: @book_comment %>
        
      </div>
    </div>
  </div>

解説:

  • id
    IDを要素に追加することで、その要素を独自に特定することができます。JavaScriptのコードでは、IDを使って特定の要素にアクセスして操作することができます。

  • book.id
    book.id は各本を特定するための識別子です。例えば、本Aの番号は1、本Bの番号は2、本Cの番号は3というように異なる値を持ちます。
    この番号を使用することで、特定の本を識別できます。識別子を指定することで、お気に入りボタンが対象の本に関連付けられ、正確にその本に対して操作を行うことができます。
    なぜ各本に対してIDを割り当てる必要があるかというと、同じページに複数の本が表示される場合、異なる本に対して異なる操作を行いたいからです。もしすべてのお気に入りボタンに同じIDを割り当てると、どの本のボタンがクリックされたのかがわかりません。
    そのため、各本には識別子(book.id)を割り当てる必要があります。これにより、JavaScriptで特定の本の要素を操作し、正確な処理を行うことができます。

book.id / @book.idの使い分け

showページでは、1つの本の詳細情報を表示するため、その本を特定するために @book.id を使用します。@book はコントローラで取得した特定の本のデータを格納しており、その本のIDにアクセスするために @book.id を使います。

indexページでは、@books 変数に全ての本のデータが格納されています。その後、indexページの <% @books.each do |book| %> のループ処理によって、各本のデータが順番に book というローカル変数に代入されます。ループのたびに book には異なる本のデータが格納されます。
このループ内で、各本のデータにアクセスするためには、ループ変数である book のID、つまり book.id を使用します。これにより、各本のIDを正確に識別することができます。

Controller編集

htmlではなく、jsファイルを読み込ませるため、コントローラを以下の通り編集します。

app/controllers/favorites.html.erb
class FavoritesController < ApplicationController
  def create
    book = Book.find(params[:book_id])
    favorite = current_user.favorites.new(book_id: book.id)
    favorite.save
-   redirect_to request.referer
  end
  
  def destroy
    book = Book.find(params[:book_id])
    favorite = current_user.favorites.find_by(book_id: book.id)
    favorite.destroy
-   redirect_to request.referer
  end
end

解説:
redirect_toを削除します。非同期通信を行う場合は、JavaScriptファイル(.js.erb)を使用してビューを更新します。アクション内にrenderredirect_toの記述がない場合、Railsは自動的に対応するJavaScriptファイルを探しに行きます。例えば、create.js.erbという名前のファイルがあれば、それが読み込まれます。

js.erbファイル作成

js.erbファイルを作成します。

app/views/favorites/create.js.erb
$("#favorite_btn_<%= @book.id %>").html("<%= j(render 'favorites/btn', book: @book) %>");
app/views/favorites/destroy.js.erb
$("#favorite_btn_<%= @book.id %>").html("<%= j(render 'favorites/btn', book: @book) %>");

解説:

  1. $("#favorite_btn_<%= @book.id %>"):
    JavaScriptのセレクタを使用して特定の要素を選択しています。
    @book.idは現在の本のIDを参照しており、セレクタは「idがfavorite_btn_〇〇」という要素を選択します。ここで〇〇は実際の本のIDに置き換えられます。

  2. .html(...):
    選択した要素のHTMLコンテンツを変更するためのコードです。

  3. "<%= j(render 'favorites/favorite-btn', book: @book) %>":
    部分テンプレートに@bookを渡し、'favorites/btn'`という部分テンプレートを呼び出します。


いいね機能の非同期通信化でした☆近日中にAjaxについてもまとめてみようと思います。
追記:以下の通りまとめましたので参考になれば嬉しいです☆
https://zenn.dev/ganmo3/articles/ced1ba8bfcacde

続きはこちら。
https://zenn.dev/ganmo3/articles/ec9f8e166e223d

Discussion