この記事はこんな人向け
- rails で非同期のいいね機能を実装したい
- この記事の前編を読んだ(まだの方は以下から)
はじめに
前提
この記事は、いいね機能実装の<後編>となるので、前編を読んでない方は、まずこちらからどうぞ。
今回紹介する実装内容は、<前編>で紹介した同期的ないいね機能が実装されていることを前提としています。
自分のコードと多少違う部分があっても、仕組みは同じなので読み替えて進めてみてください。
今回やること
- 非同期処理での”いいね”実装
- “いいね”のデザインを整える
- (おまけ)リファクタリング
非同期処理での”いいね”実装
技術的な説明(ざっくり)
非同期処理には Ajax(エイジャックス)という技術が使われています。
簡単に言うと、Webページをリロードせずとも、裏でサーバーへリクエストした内容が反映されるものです。
図に示すと以下のような感じで、ざっくり「JSが間に挟まって何かしてくれるやつ」だと理解しておけば大丈夫です。
コードの修正
ビューの修正だけで非同期に
実はrailsでの非同期通信を実装するのはとても簡単になっていて、ビューファイルを少し修正するだけでAjaxの流れを作ってくれるんです。
app/views/books/index.html.erb
<% @books.each do |a_book| %>
<div id="favorite_book_<%= a_book.id %>">
<% if favorite = a_book.favorite.find_by(user_id: current_user.id) %>
<%= link_to "いいね済", book_favorite_path(a_book.id), method: :delete, remote:true, style: "color: red;" %>
<% else %>
<%= link_to "いいね", book_favorite_path(a_book), method: :post, remote:true %>
<% end %>
<%= a_book.favorite.size %>
</div>
<% end %>
<前編>で紹介したコードから変更した点は以下の2つです。
divタグのidを追加
上記コードの2行目でdivタグにidを付与しています。
このidは後ほどJSの部分で使うために追加した記述です。id名の末尾でBookのidを含めるようにしているのは、このタグのidを一意にするためで、Bookのidが含まれていないと”いいね”した後に全部”いいね済”になってしまいます。
remote:trueを追加
link_to それぞれに新たに remote: true の属性を追加しています。
これをやることで、リンクが押されるとAjaxで処理を行うようになります。
試しにこの状態でリクエストを確認してみると、JSになっていることがわかります。
コントローラーのリダイレクトを削除
app/controllers/favorites_controller.rb
def create
Favorite.create(favorite_params)
# redirect_to books_path ←この行を削除
end
この状態で画面を見てみると、”いいね”しても画面が変わらなくなったことが確認できます。
コントローラーでのレコード登録処理は生きているので、リロードすれば”いいね”された状態になります。
jsファイルを作成
非同期で処理した結果を画面に反映するために、新たにjsファイルを作成します。
ファイルの作成ルールは、「app/views/モデル名/アクション名.js.erb」です。
なので今回は、create.js.erb と destroy.js.erb を作成します。
app/views/fovorites/create.js.erb
$('#favorite_book_<%= params[:book_id] %>').html('<%= link_to "いいね済", book_favorite_path(params[:book_id]), method: :delete, remote:true, style: "color: red;" %>');
このコードの最初の部分で、ビューで指定したdivタグのid名を使っています。
そのdivタグ内の中身が .html(…) の箇所で、link_to でいいね済のリンクを画面に反映するようにしています。
js が実行される流れ
非同期処理を組む前は、HTMLとRuby(コントローラー)だけで処理が完結していました。
このため、コントローラー → HTML という処理の流れが分かっていたと思います。
新たに JS が登場したことで、この流れは以下のようになりました。(覚えておくと理解しやすいです)
いいね数
この状態で”いいね”してみましょう。
画面に内容は反映されますが、いいね数が取れていないので、これを取得するためにコントローラーを修正します。
app/controllers/favorites_controller.rb
def create
Favorite.create(favorite_params)
@book = Book.find(params[:book_id]) #この行を追加
end
app/views/fovorites/create.js.erb
$('#favorite_book_<%= params[:book_id] %>').html('<%= link_to "いいね済", book_favorite_path(params[:book_id]), method: :delete, remote:true, style: "color: red;" %> <%= @book.favorite.size %>');
上のコードは先ほどのコードに、<%= @book.favorite.size %> を追加しただけです。
これで”いいね”した後にもリアルタイムで数が反映されるようになりました。
destroy側も同様に修正
app/controllers/favorites_controller.rb
def destroy
if favorite = Favorite.find_by(favorite_params)
favorite.destroy
end
@book = Book.find(params[:book_id])
# redirect_to books_path ←この行を削除
end
app/views/fovorites/destroy.js.erb
$('#favorite_book_<%= params[:book_id] %>').html('<%= link_to "いいね", book_favorite_path(@book), method: :post, remote:true %> <%= @book.favorite.size %>');
これで”いいね”も”いいね済”も非同期で動作するようになりました!
見た目を修正
最後に、”いいね”が文字列のままだとデザインとしてイマイチなので、FontAwesome でアイコンにしていきます。
FontAwesome
FontAwesomeは多くの方が既に入れていると思いますので、軽く定義箇所の紹介だけしておきます。
app/assets/stylesheets/application.scss
ビューファイル
以下の通り、”link_to” を do の形に変更して、iタグでアイコンを定義してあげます。
app/views/books/index.html.erb
<div class="col-sm" id="favorite_book_<%= a_book.id %>">
<% if favorite = a_book.favorite.find_by(user_id: current_user.id) %>
<%= link_to book_favorite_path(a_book.id), method: :delete, remote:true, style: "color: red;" do %>
<i class="fas fa-thumbs-up"></i>
<% end %>
<% else %>
<%= link_to book_favorite_path(a_book), method: :post, remote:true do %>
<i class="far fa-thumbs-up"></i>
<% end %>
<% end %>
<%= a_book.favorite.size %>
</div>
まとめとおまけ
まとめ
非同期通信での処理は思ったほど苦労せず、簡単に実装できることがわかりましたね!
今回のポイントをざっくりまとめると
- ビュー側で remote: true →非同期に!
- コントローラーのリダイレクトを削除
- js.erb ファイルを作成&記述
リファクタ
“いいね”のリンク部分がHTMLとJSの2箇所に同じ記述をしてしまっていたので、パーシャルビュー(部分テンプレート)を用いたリファクタリング結果を載せておきます。
※create側のみ載せるので、同じようにdestroyをやってみてください。
app/views/books/index.html.erb
<div id="favorite_book_<%= a_book.id %>">
<% if a_book.favorite.find_by(user_id: current_user.id) %>
<%= render '/favorites/thumb_done', book: a_book %>
<% else %>
<%= render '/favorites/thumb_yet', book: a_book %>
<% end %>
</div>
app/views/favorites/_thumb_done.html.erb
<%= link_to book_favorite_path(book.id), method: :delete, remote:true, style: "color: red;" do %>
<i class="fas fa-thumbs-up"></i>
<% end %>
<%= book.favorite.size %>
app/views/fovorites/create.js.erb
$('#favorite_book_<%= params[:book_id] %>').html('<%= j( render '/favorites/thumb_done', book: @book ) %>');
これで “link_to” を複数箇所に書く必要がなくなりました!
例えば”いいね”のリンクをハートマークにしたくなった場合にも、パーシャルビューの修正だけで対応できるようになりますね。
コメント