Rails いいね機能の非同期通信化(Ajax)
開発環境
ruby 3.1.2p20
Rails 6.1.7.4
Cloud9
前提
・Userモデル、Bookモデル、Favoriteは作成済み
・Devise導入済み
非同期通信とは
例えば、
<%= link_to book_favorites_path(book), method: :delete do %>
<i class="fas fa-heart" aria-hidden="true" style="color: red;"></i>
<%= book.favorites.count %> いいね
<% end %>
このようなコードがある時。
いいねボタンをクリックすると、ページがリロードされて、新しいページが作成されます。
このように、ページ全体がリロードされて、新しいページが作成される状態のことを、同期通信といいます。
Rails6の場合、デフォルトで同期通信状態になっています。
逆に非同期通信(Ajax)は、データの送信をした際にページをリロードしないで、変更のあった部分だけを取り急ぎ、JavaScriptで書き換えています。
「いいね」や「コメント」をする度にページをリロードするのは不便なので、非同期通信を使うようにします。
step1 jQueryの読み込み
非同期通信を行うときは、JacaScript と jQuery を使用します。
まずはjQueryを読み込みます。
$ yarn add jquery
上記コマンドを実施します。
次に、config/webpack/environment.js に以下のコードを記述します。
const { environment } = require('@rails/webpacker')
// 追加ここから
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jQuery: 'jquery/src/jquery'
})
)
// 追加ここまで
module.exports = environment
最後に、app/javascript/packs/application.js に以下を追記します。
require("jquery") //このコードを追加
これで準備が整いました。
step2 いいねボタンにAjaxの処理を適用
次に、いいねボタン部分にAjaxの処理を適用させます。
...
<% if book.favorited_by?(current_user) %>
<%= link_to book_favorites_path(book), method: :delete, remote: true, data: {"turbolinks" => false} 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, data: {"turbolinks" => false} do %> #ココ
<i class="fas fa-heart" aria-hidden="true"></i>
<%= book.favorites.count %> いいね
<% end %>
<% end %>
...
上記コードのうち、link_to に含まれる
remote: true
data: {"turbolinks" => false}
の2つを追記しました。
remote: true を追記することで、非同期通信状態になります。
また、data: {"turbolinks" => false} を追記して、
限定的に turbolinks が動かないようにしています。
この記述がない場合、リロードしないと処理が表示されません。
step3 いいね保存/削除のリダイレクトを削除
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
favoritesコントローラのcreate, destroyともに
redirect_to request.referer を使用していましたが、
この部分をコメントアウトします。
そうすることで、
リダイレクト先がない状態 & 非同期通信(step2) になり、
createアクション実行後は、create.js.erb を
destroyアクション実行後は destroy.js.erb を探すようになります。
step4 いいねボタンのテンプレート化
非同期通信で更新したい部分(いいねボタン)をテンプレート化します。
<% if book.favorited_by?(current_user) %>
<%= link_to book_favorites_path(book), method: :delete, remote: true, data: {"turbolinks" => false} 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, data: {"turbolinks" => false} do %>
<i class="fas fa-heart" aria-hidden="true"></i>
<%= book.favorites.count %> いいね
<% end %>
<% end %>
step5 テンプレート読み込み部分のid設定
step4 で作成したテンプレートを読み込む際、注意点があります。
下記のように、読み込む部分には idを設定してください。
このidは step6で使用します。
...
<table class='table'>
<tr>
<td><%= link_to(@book.user) do %>
<%= image_tag @book.user.get_profile_image, size:"100x100" %><br>
<%= @book.user.name %>
<% end %>
</td>
<td><%= link_to @book.title, @book %></td>
<td><%= @book.body %></td>
<td>コメント数: <%= @book.book_comments.count %></td>
<td id="favorite_buttons_<%= book.id %>"> #この部分
<%= render "favorites/btn", book: @book %>
</td>
<% if @book.user == current_user %>
<td><%= link_to 'Edit', edit_book_path(@book), class: "btn btn-sm btn-success edit_book_#{@book.id}" %></td>
<td><%= link_to 'Destroy', @book, method: :delete, data: { confirm: '本当に消しますか?' }, class: "btn btn-sm btn-danger destroy_book_#{@book.id}"%></td>
<% end %>
</tr>
</table>
...
上記の
<td id="favorite_buttons_<%= book.id %>"> #この部分
<%= render "favorites/btn", book: @book %>
</td>
この部分のように、_favorites/btn.html.erb を読み込む部分に idを設定します。
step6 JSファイルの作成
最後に、js.erbファイルを作成します。
step3 で削除したリダイレクトの代替部分です。
$('#favorite_buttons_<%= @book.id %>').html("<%= j(render "favorites/btn", book: @book) %>");
$('#favorite_buttons_<%= @book.id %>').html("<%= j(render "favorites/btn", book: @book) %>");
$('#favorite_buttons_<%= @book.id %>')
この部分は、jQueryのセレクターです。後半に書かれている処理は、#favorite_buttons_<%= @book.id %> に適用されます。
.html()
この部分はjQueryのメソッドです。引数に書かれたhtmlコンテンツを設定しています。
"<%= j(render "favorites/btn", book: @book) %>"
これはRubyのコードをJavaScriptの文字列中に埋め込んでいます。
jは escape_javascript と同じ意味です。
escape_javascript とは、調べてみてもなんだかよくわかりませんでしたが、、、
「RubyのコードをJavaScript中に書くときに、そのままだとうまくいかないことがあるが、
escape_javascript を使っておけばいい感じに変換してくれるので安心」みたいな感じでした。
render "favorites/btn", book: @book
これはよく見るパーシャルテンプレートの読み込みです。
確認すべきはログとコンソール部分
非同期通信(Ajax)処理を使い始めると、プレビュー画面にエラー文が表示されないことが増えます。
動作がうまくいかない場合は、ターミナルのログや、開発ツールの Console部分を確認するとエラーが出ていることがあります。
参照
Discussion