【Ruby on Rails】部分テンプレート(js.erb)を用いた非同期通信について(基礎/開発)


最初に

自身で学習してきた内容を書き出していきます。
基本的には自分自身でわかるような内容になりますので、ご容赦ください。
また、誤っている点がありましたら、コメントにてご指摘ください。

(要確認)マークがあるものは、「実行前に必ずググる」べき内容。

本記事の注意点

こちらでは、いいね機能とコメント機能の非同期通信化のメモが書いてありますが、
いいね機能とコメント機能の実装部分は記述しておりません
飽く迄、個人的なメモ書きであり、「非同期通信」について記述しております。
ご容赦ください。

index

  • そもそも非同期通信とは
  • Ajaxとは
  • renderとredirect_toの違い
  • 記述例

そもそも非同期通信とは

大雑把に表現すると、「画像の遷移のない通信」のこと。
つまりは、同期処理は一瞬画面が白くなって、画面を切り替わることである。

Ajaxとは

「Asynchronous JavaScript + XML」の略称。(Asynchronous:非同期)
JavaScriptの非同期通信を使って、Webサーバ間とデータのやり取りを行える。

なので今回は、js.erbファイルを用いた非同期通信を実装してみる。

renderとredirect_toの違い

js.erbファイルを用いる為、renderを使用するが、ここで同じような動きをするredirect_toとの違いについて知っておくと良い。

大雑把に説明すると、「MVCの動き方が違う遷移方法」と言った感じ。

新規投稿後、詳細画面へredirect_toする動き

赤色のredirect_toで動きがわかるように、一度ルーティングへ戻り、xxx#showとして遷移しているのがわかる。
renderの場合、ここがそのままviewに渡り、出力されます。

記述例

コメント機能の場合

コメント表示欄をrenderで記述。

show.html.erb
<tbody id="comments_index"> # js発火させる為、id指定
  <%= render 'memos/comment', memo: @memo %> # @memoはcommentコンロトーラーとmemoコントローラー共通のデータ
</tbody>

部分テンプレートに記述。

memos/_comment.html.erb
<% memo.memo_comments.each do |memo_comment| %>
<tr>
  <td><%= attachment_image_tag memo_comment.user ,:profile_image, :fill, 50, 50, format: 'png', fallback: "no_image.jpg", size:'50x50' %></td>

  …

</tr>
<% end %>

ここまでだと、まだ非同期になっていない。
今回の場合だと、新規投稿(create)と投稿削除(destroy)の処理時に非同期になってほしいので、下記を作成し記述。

views/memo_commnets/create.js.erb
$('#comments_index').html("<%= j(render 'memos/comment', memo: @memo) %>", ); # 発火して欲しい箇所のID記述と、render記述
$(".text_field").val(""); # 発火した際のtext_fieldを空白に更新するように指定
views/memo_commnets/destroy.js.erb
$('#comments_index').html("<%= j(render 'memos/comment', memo: @memo) %>"); # 発火して欲しい箇所のID記述と、render記述

まだ、memocommentsのcontrollerに「@ memo」が定義されていないので、追加記述。

controllers/memo_commnets_controller.rb
def create
    @memo = Memo.find(params[:memo_id])
    @memo_comment = current_user.memo_comments.new(memo_comment_params)
    @memo_comment.memo_id = @memo.id
    if @memo_comment.save
      # redirect_to memo_path(@memo)
    else
      @user = @memo.user
      @newmemo = Memo.new
      render template: "memos/show"
    end
  end

  def destroy
    # binding.pry
    @memo = Memo.find(params[:memo_id])
    # ページからコントローラーにコメントのIDを持ってくる・・・
    # /memos/#{@memo.id}/memo_comments/#{@memo_comment.id}/を作り上げる=memo_comment.idを探して、デストロイさせる!!
    @comment = MemoComment.find(params[:id])
    @comment.destroy
    # redirect_back(fallback_location: root_path) #非同期通信の為削除
  end

少し補足すると、memo: @memoで定義した内容が同じデータ情報出なくてはならない。

また、その時にform_tagで記述していた場合、form_with記述にする必要がある。
理由として、「デフォルトでremote: true」されているから。(XMLHTTPRequestオブジェクトリクエストを送っている)

注意点
remote: trueがかかっている場合、redirect_toは機能しないので焦らない。

もし上手くいかなったら、pry-baybeg gemなどで一回一回止めて、変数や引数がきちんと渡っているかを確認しながら、調整していくこと。

いいね機能の場合

コメント機能の非同期通信とほとんど変わらない処置を施すが、ポイントがいくつかある。

POINT

  • each文で繰り返し表示させる場合、発火させたいrenderを囲む、divタグにそれぞれのIDを付け加える
  • いいね機能の非同期通信の範囲をきちんと理解する
show.html.erb
<% @memos.each do |memo| %>
  <tr>
    <td>
      <div id="likes_buttons_<%= memo.id %>">
        <%= render 'memos/favo', memo: memo %>
       </div>
     </td>
   </tr>
<% end %>

「どの投稿の、良いねがしたいのか」を判別する為、
"likes_buttons_<%= memo.id%>"でeach文繰り返ししているIDを振り分けてあげる。

んで、部分テンプレートに記述。

memos/_favo.html.erb
<% if user_signed_in? %>
  <% if memo.favorited_by?(current_user) %>
    <%= link_to memo_favorites_path(memo), method: :delete, remote: true do %>
      <span class="glyphicon glyphicon-heart" aria-hidden="true" style="color: red;"></span>
    <% end %>
  <% else %>
    <%= link_to memo_favorites_path(memo), method: :post, remote: true do %>
      <span class="glyphicon glyphicon-heart" aria-hidden="true" style="color: gray;"></span>
    <% end %>
  <% end %>
  <%= memo.favorites.count %> イイね!
<% end %>

イイね機能に関しては当記事で説明はしていないので、ご容赦ください。

views/favorites/create.js.erb
$('#likes_buttons_<%= @memo.id %>').html("<%= j(render 'memos/favo',memo: @memo) %>");
views/favorites/destroy.js.erb
$('#likes_buttons_<%= @memo.id %>').html("<%= j(render 'memos/favo',memo: @memo) %>");

薄々感づいているかもしれないが、xxx.js.erbでの記述は下記に乗っ取っている。
$('発火箇所のID指定').html("<%= j(render箇所の指定);

今回の場合では、メモ(memo)とそれに紐ずくコメント(commnet)とイイね(favorite)が存在する。
render箇所の指定時に、「memoコントローラー」と「コメントやイイねコントローラー」で、memo: @memoが同じものを代入している必要がある点に注意!

仮に、memoコントローラーでは@memo = memo.find(params[:id])とかで指定していて、commentコントローラーでは@memoを指定していなかったら、jsは発火しない。(つまり同期通信のまま)

上記の意味が理解できると、外部APIから取得したデータをDBに格納しないままアソシエーションを組み、非同期通信にさせる事なんかもできちゃう。
これに関しては、またいつか投稿します。

追加あれば、どんどん更新していきます。

駆け出しとして頑張ります。