#はじめに
こんにちは!閲覧ありがとうございます。
今回は【いいね機能の非同期通信化(Ajax)】を学習した為、
復習の意味も込めてアウトプットとしてまとめていこうと思います。
未来の僕が見返した時に、
また、プログラミング初学者が見てくれた時に、
有益な情報であるよう僕なりに書いていきます。
※誤字、脱字、知識の誤りなど見つけた方はご教授いただけると幸いです。
#目次
0:はじめに
1:開発環境
2:前提条件
3:処理の流れ
4:実装
5:さいごに
#開発環境
・ruby: 2.6.3
・rails: 5.2.4.5
・OS: macOS Catalina ver10.15.7
・Cloud9
#前提条件
下記3つはすでに実装済で話を進めます。
1:User(ユーザー)
2:book(投稿)
3:favorite(いいね)
※本を投稿するWebアプリを例にしている為、bookモデルを作成しています。
(postで作っている方が多いかも)
・device導入
※ここではCRUD機能基礎やアソシエーション、いいね機能そのものについての説明は割愛します。
#実装の流れ
Ajaxの概要についてはこちらの記事がとても参考になりました。
初心者目線でAjaxの説明
簡単に言い換えるなら、
webブラウザからのリクエストに対し、JavaScriptを使って、 画面を遷移せずに(リロードすることなく)指定されたリクエストを行う。
例
ユーザーがいいねボタンを押す
↓
(ページは変わらずに)
画面のいいねボタンのところだけ変更される(いいねが増える)
#非同期通信 処理の流れ
実際の処理の流れは下記のようになっています。
1:ユーザーが__いいねをクリックする(送信する)__
(Webブラウザ)
↓
2:ルーティングでどのコントローラのどのアクションを呼び出すかを定める
(route.rb)
↓
3:アクションを実行する[いいねの保存や削除]
(favorites.controller.rb)
↓
4:コントローラ名/アクション名.js.erbファイル内の処理を実行する[部分的にページを更新]
(views/favorites/create.js.erb,
views/favorites/destroy.js.erb)
↓
5:レスポンスを返す
(Webブラウザ)
## 実装
ここからは順を追って実装方法をまとめていきます。
####■jQueryの読み込み
下記、該当箇所に追記
gem 'jquery-rails'
bundle install 忘れずに!
//= require jquery
//= require rails-ujs
####■いいねボタンにAjaxの処理を適用させる
<% @books.each do |book| %>
<td>
<%= link_to book.title, book_path(book.id) %>
</td>
<td>
<%= book.body %>
</td>
<td>
<% if book.favorited_by?(current_user) %>
<%= link_to book_favorites_path(book), method: :delete, remote: true do %> #remote: trueを追加
♥<%= book.favorites.count %>いいね
<% end %>
<% else %>
<%= link_to book_favorites_path(book), method: :post, remote: true do %> #remote: trueを追加
♡<%= book.favorites.count %>いいね
<% end %>
<% end %>
</td>
<% end %>
link_to内に`remote; true` を追加しています。 これにより、HTMLリクエストではなく、JavaScriptのリクエストがfavoritesコントローラに送られます。
__HTMLリクエスト__の場合
→データベースにいいねを保存or削除し、画面にリダイレクトする(ページを読み込む)
__JavaScriptリクエスト__の場合
→データベースにいいねを保存or削除し、JavaScriptを使っていいね部分のみ更新する(ページ読み込みなし)
###■いいね保存、削除のリダイレクト先を消す。
class FavoritesController < ApplicationController
before_action :authenticate_user!
def create
@book = Book.find(params[:book_id])
favorite = current_user.favorites.new(book_id: @book.id)
favorite.save
redirect_back(fallback_location: root_path) #ここを削除!
end
def destroy
@book = Book.find(params[:book_id])
favorite = current_user.favorites.find_by(book_id: @book.id)
favorite.destroy
redirect_back(fallback_location: root_path) #ここを削除!
end
end
リダイレクト先を削除したことにより、
リダイレクト先がない、かつJavaScriptリクエストという状況になり、
createアクション実行後は、create.js.erbファイル
を、
destroyアクション実行後はdestroy.js.erbファイル
を探すようになります。
###■更新したい箇所を部分テンプレート化する
<% if book.favorited_by?(current_user) %>
<%= link_to book_favorites_path(book), method: :delete, remote: true do %>
♥<%= book.favorites.count %>いいね
<% end %>
<% else %>
<%= link_to book_favorites_path(book), method: :post, remote: true do %>
♡<%= book.favorites.count %>いいね
<% end %>
<% end %>
少しややこしいですが、「2:いいねボタンにAjaxの処理を適用させる」のコードから、
更新したい部分だけを切り取って部分テンプレートにしています。(いいねボタンといいね数)
###■部分テンプレートを読み込みます。
<% @books.each do |book| %>
<tr>
<td>
#本のタイトル、本詳細ページ(show)へのリンク
<%= link_to book.title, book_path(book.id) %>
</td>
<td>
#本の内容
<%= book.body %>
</td>
#部分テンプレートにした箇所(部分的に更新したい箇所)
<td id="favorite_buttons_<%= book.id %>">
<%= render "favorites/favorite", book: book %>
</td>
</tr>
<% end %>
__point1__ eachメソッド内でのrender処理なので、 インスタンス変数は存在しません。bookをbookに渡しています。(`book: book`)
point2
この箇所の更新を指定するために、変更したい箇所にidで名前をつけます。
id="favorite_buttons_<%= book.id %>"
###■js.erbファイルの編集
$('#favorite_buttons_<%= @book.id %>').html("<%= j(render "favorites/favorite", book: @book) %>");
$('#favorite_buttons_<%= @book.id %>').html("<%= j(render "favorites/favorite", book: @book) %>");
指定したセレクタ(idの部分)のみHTMLをrenderして部分的に更新します。
これで完成です。
#さいごに
実装の流れを簡単にまとめるとこのようになります。
1:link_to文
にremote: true
を追加
2:create,destroyアクションのリダイレクト先を削除
3:部分テンプレートの作成
4:部分テンプレートの読み込み
5:js.erbファイル
にて部分テンプレート箇所のみ更新処理
・個人的な感想
render内で読み込む部分テンプレートにどのような変数を渡すのかを間違えないように意識することが大事だと思いました。
今までクラス名やid名の指定で-(ハイフン)を使っていましたが、_(アンダーバー)と見間違えて、js.erbファイル内でのセレクタの指定がうまくできず、半日無駄にしてしまいました。今後、-(ハイフン)の使用は禁止しようと思います。
以上です。
拙い知識でわかりづらい文章だと思いますが、最後まで閲覧ありがとうございました。
少しでも誰かの役に立っていたら幸いです。
この記事について誤っている部分や改善点等あればコメントしていだけると助かります。