今回はRailsでのremote:trueとjs.erbファイルを用いた非同期通信について、わかりやすさ重視でまとめてみました。
非同期通信を用いたいいね機能のデモ動画及びコードの実例を使いながら説明していきます。
こちらをご理解いただければ、本当に簡単に非同期通信が実装できるようになります。
一見難しく思われるかもしれませんが、是非最後まで読んでいただければと思います。
わかりやすさを重視しているので、厳密にいうとそれって違うよ!っていう点もあるかと思います。
そういった点についてはコメントにて優しくご指摘、もしくはこっそり編集リクエストいただければ幸いです。笑
そもそも非同期通信とは
AjaxはもともとAsynchronous JavaScript + XMLの略で、Webブラウザ上で動作するJavaScriptでサーバからXMLデータを取得し、取得したデータをコンテンツに動的に反映するという手法
ものすごく大雑把にまとめると、サーバからデータを取得するけど、ページ遷移は行わずに、取得したデータを使って表示の一部だけを変更する処理です。
ページ遷移を行わないので、早く、かつスムーズな動きを実現できます。
Ajax自体の概要については前回まとめましたので、そちらをご参照いただければと思います!
Ajax(非同期通信)についてわかりやすさ重視でまとめてみた(Rails使用のデモ付)
デモの概要
今回の記事で紹介する非同期通信の実例は、いいね機能です。
こちらは某有名マッチングサイトのクローンサイトを作った時に実装したものです。
いいねをする
↓
いいねボタンの表示が変わる
&
残りいいね数が変わる
この裏でDB上にいいねは作成されているんですが、ページ遷移は行われません。
つまり非同期でいいねを行ったという訳です。
remote: true とは
link_to, form_for などに対してオプションで設定することができます。
=link_to "いいね!", user_likes_path(user_id: user.id), method: :post, remote: true, class:"button_like"
-#=> <a class="button_like" data-remote="true" data-method="post" href="/users/"ここにはuser_idが入る"/likes">いいね!</a>
これにより、何が変わるのか。
一言で言うと、**リクエストがhtml形式ではなくjs形式になる!**ということだけなんです。
js形式とhtml形式
普段なにも意識せずにlink_toやform_forを使う際に飛んでいるのはhtml形式のリクエストです。
では、js形式とhtml形式、何が違うのかを以下で説明していきます。
##html形式
普段のページ遷移時はhtml形式のリクエストを送り、htmlファイルを要求しています。
MVCでいうと、
####① ブラウザからhtml形式のリクエストを送る
↓
####② ルーティングでどこのコントローラで何のアクションをするのかを判定
↓
####③ コントローラにてアクション内の項目を実行(formatで場合分けしている際には、format.html内のアクションを実行)
↓
####④ コントローラのアクションに応じてモデルの内容を更新、削除、インスタンスの取得などを行う
↓
####⑤ app/views/コントローラ名/アクション名.html.erb(hamlの場合、html.haml, slimの場合、「html.slim」)を探し出し、レンダリングする
という一連の流れです。
js形式
js形式のリクエストを送る場合は、 この中の①と③と⑤が変わります。
####①' ブラウザから'js'形式のリクエストを送る
↓
####② ルーティングでどこのコントローラで何のアクションをするのかを判定
↓
####③' コントローラにてアクション内の項目を実行(formatで場合分けしている際には、format.js内のアクションを実行)
↓
####④ コントローラのアクションに応じてモデルの内容を更新、削除、インスタンスの取得などを行う
↓
####⑤' app/views/コントローラ名/アクション名.js.erbを探し出し、レンダリングする。
今の一連の流れの中で、最大の違いは⑤のレンダリング、つまり出力するファイルです。
コントローラは**「app/views/コントローラ名/アクション名.リクエストの形式.〇〇」**というファイルを探しに行きます。
それゆえ、js通信ではhtmlファイルをレンダリングしません。
つまりページ遷移を行わないんです。
では、その代わりにレンダリングするjs.erbファイルって何をするファイルなんだ!ということなんですが、、もうお気付きの方もいらっしゃるかもしれないですが、「js」の名前の通り、javascriptを実行します。
つまり、aタグを押す(あるいはsubmitを押す)と、コントローラでのアクションに伴ってjsファイルを実行するんです。
さらに、これただのjsファイルではなく、js.erbファイルなんです。
つまり、ERBタグ(<% %>, <%= %>)が使えるんです。
それゆえに、js.erbではコントローラで定義したインスタンスを用いることができるんです。
そのため、今回のアクションで更新された値を持ったインスタンスの情報を反映させることもできるし、そのインスタンスを使った部分テンプレートのレンダリングもできるんです!
実例
###前準備
今回の例では、js.erbファイルではjqueryを使います。
jqueryはgem 'jquery-rails'を入れてあげた上で、appplication.jsでrequireしてあげてください。
そして、今回の非同期通信を行う上で、rails-ujsというライブラリが必要となります。
rails-ujsはRails5.1よりデフォルトで搭載されているので、application.jsにてrequireしてあげるだけで大丈夫です。
ちなみにRails5.1より前のバージョンの場合は、rails-ujsではなく、jquery_ujsをrequireしてあげてください。
この場合、必ずjqueryを先にrequireするようにしてください。
-# バージョンがRails5.1以降の場合
//= require jquery
//= require rails-ujs
-# Rails5.1より前の場合
//= require jquery
//= require jquery-ujs
### users#index画面 users#index画面でいいね残高とユーザの一覧を表示させています。
-# users#index画面におけるいいね残高表示部分のhtml
%span.like_balance
=current_user.like_balance
人
-# (中略)
-# userのプロフィール用部分テンプレートを呼び出します
.box_list_view_user
%ul.list_view_users
=render partial: "users/user_small_profile", collection: @users, as: "user"
### プロフィール用部分テンプレート 呼び出されるプロフィール用部分テンプレートがこちらです。 ポイントはlikeのボタンの親要素のidをユーザごとに定義している部分です。 こうすることで、jsファイルでいいねボタンを変える際に、その親要素を特定できるようになります。
-# userのプロフィール用部分テンプレートにおけるlikeボタン周辺のhtml
.box_button_like
#like-button-zone{ id: user.id }
-# userのidが1の場合、id="like-button-zone_1"になります
= render partial: "likes/like", locals: { user: user }
### いいね!ボタン用部分テンプレート こちらはいいね!ボタンの部分テンプレートです。
-# if文の中身はモデルにて定義済のインスタンスメソッド
-# 既にcurrent_userがuserをlikeしているか判定している
- if current_user.like?(user)
- #すでにいいねしている場合は"いいね済"の表記に変わり、無効なリンクになる
=link_to "いいね済", "#", class:"button_like button_orange"
- else
- # そうでない場合はいいね!ボタンが表示される
=link_to "いいね!", user_likes_path(user_id: user.id), method: :post, remote: true, class:"button_like button_pink"
### likesコントローラ 続いて、likesコントローラで、likes#createアクションを見てみましょう。 わざわざここで sending_user と reeceiving_user を定義しているのは、 後ほど js.erb ファイルにて利用するからです。
def create
# create.js.erbで用いるインスタンスをここで生成します。
@sending_user = User.find(current_user.id)
@receiving_user = User.find(params[:user_id])
# いいねの残高を1減らした上でいいねを作成しています。
new_like_balance = @sending_user.like_balance - 1
@sending_user.update(like_balance: new_like_balance)
@like = Like.create(sender_id: @sending_user.id, receiver_id: @receiving_user.id)
end
### create.js.erbファイル いよいよ一番の肝、js.erbファイルです。 こちらのファイルは自動生成されないので自分で作成します。 「app/views/コントローラ名/アクション名.js.erb」の形で作成しましょう。
コードはたったこれだけです。これだけで非同期通信が実装できるんです。
// ERBタグを用いることでコントローラにて定義した@receiving_userの値を取得できます
// また同様にERBタグを用いて部分テンプレートも適用できます(jを用いてエスケープ処理を行う必要あり)
$("#like-button-zone_<%= @receiving_user.id %>").html("<%= j(render partial: 'likes/like', locals: { user: @receiving_user }) %>");
// いいね残高をコントローラにて定義した@sending_userを用いて表示します
$(".like_balance").html("<%= @sending_user.like_balance %>")
大事なのはこのコードを実行する順番です。
このファイルではまずERBタグ(<% %>, <%= %>のタグです)内のコードを実行します。
その後にjsアクションを実行する、という流れですね。
ERBタグを最初に読み込むので、例えばいいねを受け取るreceiving_userのidが1であれば、
$("#like-button-zone_1")に対して、部分テンプレートを適用する、ということができる訳ですね!
かつ、送ったユーザのいいね残高が今回のアクションを経て30から29になったとしたら、
二つ目のjsのコードは$(".like_balance").html("29")になる訳です。
ERBタグのコードが実行されて、更にその結果が入ったjsのコードが実行されることによって
非同期通信ができる、ということですね!
最後に
いかがでしたでしょうか。今回で remote:true と js.erb の Ajax の基礎を説明させていただきました。
これに慣れるとシンプルな非同期通信は簡単に実装できるようになり、スムーズに動くサービスが作れます!
ぜひ実際にコードを書いてみて試してみてください!