【Rails】Ajax(非同期通信)による同ページ内の遷移について


タブ機能の応用ようなものを実装します!

非同期通信という言葉だけ聞くと難しいことをしていると思われるかもしれませんが、簡単にやってることを説明してしまうとWebページの一部分だけを塗り替えているだけです。
個人的なやり方ですので、指摘などある場合はコメントで教えていただけると嬉しいです!

今回の実装でこのようなことができます↓↓

そもそもajaxとは??

Asynchronous JavaScript XML」の略で「Ajax」。

「Asynchronous」が「非同期」なので、直訳すると「非同期JavaScript+XML」ですね。

「JavaScriptの非同期通信を使って、XML形式のデータをWebサーバから貰ってくる」のがAjaxです。
「Ajax」自体が何かの技術や言語を指すわけではありません。あくまで実装方式の呼び名らしいです。

同期通信と非同期通信の違い

同期通信の場合

webブラウザからサーバーにリクエストを通信し、レスポンスが戻ってくる。この時に、すべての情報を通信しているので、一瞬画面が白くなる。

=> サーバーからレスポンスが返ってくるまでは他の作業はできない。

非同期通信の場合

webプラウザから一部の情報をリクエストするので、それ以外の部分は変わらない。なので画面が白くなることがない。

=> サーバーからレスポンスが返ってこなくても他の作業ができる。

全体の流れの把握

①リンクを押す → ②指定したコントローラーのアクションに指示が飛ぶ → ③〇〇コントローラーの〇〇アクションに記載した情報を保持 → ④〇〇アクションと同じ名前のjsファイルを探す → ⑤jsファイルに記載された情報を元にHTMLを一部分だけ塗り替える。

要所はこんな感じです。

実装してみよう!

前置きが少し長くなってしまいましたが、実装に入っていきましょう。

今回の趣旨とは異なるので詳しくは書かないため、前提条件だけいくつか挙げておきます。

  • ルーティングは設定済み
  • model、controller、viewなどは構築済み
  • gem "jquery-rails"導入済み
  • gem "bootstrap"導入済み

①リンクを配置する

実際のコードはこちら

〇〇.html.erb

  <div class="row top-button">
    <div class="col">
      <%= link_to "注目されている活動実績↓↓", popular_path, remote: true %>
    </div>
    <div class="col">
      <%= link_to "新着の活動実績↓↓", pickup_path, remote: true %>
    </div>
    <div class="col">
      <%= link_to "ランダムマッチング↓↓", users_path, remote: true %>
    </div>
  </div>

  <% if @popular.present? %>
    <div class="top_area">
      <%= render "posts/popular", popular: @popular %>
    </div>
  <% elsif @pickup.present? %>
    <div class="top_area">
      <%= render "posts/pickup" , pickup: @pickup %>
    </div>
  <% else %>
    <div class="top_area">
      <%= render "users/index" , users: @users %>
    </div>
  <% end %>

ここで押さえておきたいポイントが三つあります。

remote: trueを設定したリンク(トリガー)の設置
②表示させたい部分テンプレート化したビューを配置
③クラス名を明示したdivタグで部分テンプレート化したviewを囲む

まず、①オプションとしてremote: trueと追記してあげることによってリンクが押された時にajax処理が実行されます。

次に、②部分テンプレートに関しては建設したい建物(コンビニやスーパーなど)だと思ってください。
ページ全体を遷移させるのであればviewに記載する必要はないのですが、建物(ビュー)が街のど真ん中にいきなり建設されても邪魔になるだけです。それを阻止するために工事したい区画を設定する必要があります。

divタグで囲んだ場所は工事したい区画だと思ってください。
今回は、この指定した区画のビュー(建物)だけを工事したいため、プログラムがそれを理解できるように、こうして区画に名前をつけています。上記でいうところの<"top_area">という場所です。ビューを表示させたい場所は同じ区画なので、上記のように同じ区画名で囲ってあげる必要があります。

部分テンプレート化は今回の実装に大きく貢献するポイントなので、部分テンプレートがいまいち解らないという方はこちらの記事を参考にしてみてください。

まとめると、①指示(リンクを押す)を出すことで、③工事したい区画(divクラス)に、②建設したい建物(部分テンレート)を出現させようということです。

②上記のリンクで指定したコントローラーのアクションに指示が飛ぶ

 <%= link_to "新着の活動実績↓↓", pickup_path, remote: true %>

このリンクを押したことで、pickup_pathという場所に指示が飛びます。

routes.rb
get "/posts/pickup", to: 'posts#pickup', as: 'pickup'

ちなみにルーティングはこんな感じになっております。
今回は、postsコントローラーのpickupアクションに指示が飛ぶという流れになってます。

③〇〇コントローラーの〇〇アクションに記載した情報を保持

posts_controller.rb
def pickup
  @pickup = Post.limit(12).order("created_at DESC")
end

pickupアクションに記載されている情報(今回だと新着情報)を取得したかったので、Postモデルの中にあるデータを製作日時順に最大12個まで表示されるように明記しています。

この後、通常であればアクション名と同じ名前のhtml.erbファイル(今回だとpickup.html.erb)を探しに行くのですが、
remote: trueを追記したことによって指示が特殊になり、アクション名と同じ名前のjs.erbファイル(今回だとpickup.js.erb)を探しにいきます。

④〇〇アクションと同じ名前のjsファイルを探す

js.erbファイルは自動的に作られないため、自分で作成する必要があります。
今回だと app/views/posts の直下に新たに pickup.js.erb を作成してください。

pickup.js.erb
$(".top_area").html("<%= escape_javascript(render 'posts/pickup', pickup: @pickup) %>")

↓これをさっきの説明に当てはめるのなら

pickup.js.erb
$(".工事したい区画").工事の形式("<%= escape_javascript(render 建設したい建物) %>")

となります。
こうすれば察しの良い皆さんであれば、このコードが何をしたいのか一目で理解できるのではないでしょうか。
...そうです!いわばこれは設計図です。
これ通りに工事してくださいね、という情報がこの一文の中に凝縮されています。

①で部分テンプレートやクラス名を明示した理由がこの一文にあります。
部品化することで、プログラムに細かく工事内容を指示することが可能となるからです。

$(".top_area")
という区画に対して、
html("<%= escape_javascript(render 'posts/pickup', pickup: @pickup) %>")
render'posts/pickup' というファイル(建物)をHTML形式で建設しています。
pickup: @pickup
というのはpostsコントローラーで情報を詰め込んだ箱(インスタンス変数)です。

ちなみに $(".工事したい区画")の先頭は(.)だとdivクラス(#)だとidになります。

⑤js.erbファイル(設計図)に記載された情報を元にビュー(建物)を一部分だけレンダリング(工事)する

_pickup.html.erb
  <% @pickup.each do |post| %>
    <div>
      <h3>
        <%= link_to post_path(post.id), remote: true do %>
         <% post.title.truncate(20) %>
        <% end %>
      </h3>
        <%= post.likes.count %>
        <%= post.impressions_count %>
      <p>活動内容:
        <%= post.product.truncate(30) %>
      </p>

      <p>使用SKILL:
        <%= post.skill.truncate(30) %>
      </p>

      <p>By
        <%= link_to user_path(post.user), remote: true do %>
        <%= post.user.name.truncate(20) %>
        <% end %>
      </p>

      <i class="fas fa-tags"></i>
      <% post.tags.each do |tag| %>
        <%= link_to tag.tag_name, posts_path(tag_id: tag.id), class: "btn btn-outline-secondary",remote: true %>
      <% end %>

    </div>
  <% end %>

↓CSSを書いていますが、こんな感じに表示されます。

同じ要領でボタンを配置してページの遷移ができれば完成です。

最後に

最後までお付き合いいただきありがとうございます。
まだまだRailsやJavaScriptに対する知識が浅く、適切でない表現をしていたらすみません。
もっと解りやすいやり方あるかもしれませんので、参考の一つとしてみていただければ幸いです。
また、間違いなどありましたらコメントなどで教えていただけると嬉しく思います。