🚀

【Rails】Ajaxの実装方法をざっくりまとめてみた

7 min read

1.この記事をなぜ書いたか

Ajaxを忘れてしまったので、復習の意味で記事を作成しました。

記事の中で間違いがある場合、コメント頂けると嬉しいです。

2.Ajaxとは何か?

Ajaxとは、Webブラウザ上でサーバーと非同期通信を行い、ページをリロードせずにページの一部を更新する技術です。 Ajaxは技術と言いましたが、Ajaxはプログラミング言語ではありません。Ajaxは複数技術の組み合わせによって構成される技術です。以下は、Ajaxを構成する代表的な技術です。

  • JavaScriptを使ったDOMの操作
  • クライアントとサーバー間の非同期通信
  • HTMLの代わりに、JavaScriptまたはJSONを返却

AjaxをWebアプリケーションに実装することで、Webページをリロードせずに、Webページの一部分を書き換えることができます。

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

同期通信とは、クライアントとサーバー間の通信が終了するまで、次の通信を始めることができないような通信です。そのため、サーバーからHTTPレスポンスが届くまで、クライアント側は画面を操作することができません。一方、非同期通信とは、クライアントとサーバー間の通信が途中でも、別の通信を実施できるような通信のことです。そのため、サーバーからHTTPレスポンスが届いていなくても、クライアント側で画面を操作できます。クライアント側で画面が操作できるので、サーバー側で前のHTTPリクエストを処理中だとしても、クライアントは新たなHTTPリクエストを出せます。

余談ですが、rails-ujsというJavaScriptライブラリによって、Railsでの非同期通信が可能になっています。そのため、非同期通信を実施する際は、rails-ujsを必ずRailsに読み込ませましょう。rails-ujsについては以下のサイトに詳しく書いてます。

https://www.bokukoko.info/entry/2017/12/22/194327

4.DOMをすごくざっくり説明してみる

RailsでAjaxを実装する際に、DOMはすごく重要です。DOMをすごくざっくり言うと、JavaScriptとHTML文章を繋ぐインターフェースです。DOMはHTML文章を構成している一つ一つの要素をノードオブジェクトとして扱います。そのノードオブジェクトは、DOMを経由してJavaScriptで操作することができます。ノードオブジェクトの追加、変更、削除をすることで画面を動的に変化させることができます。

もし、DOMについてもっと深く知りたい方は以下の記事がオススメです。

https://zenn.dev/ak/articles/c28fa3a9ba7edb#ノードとはオブジェクトである
https://kuroeveryday.blogspot.com/2018/11/difference-between-dom-and-node-and-element.html

5.RailsでどのようにAjaxを実装するのか?

Railsでは、2つの方法でAjaxを実装できます。しかし、今回はHTTPレスポンスとしてJavaScriptを返すタイプのAjaxを実装します。

以下の手順でAjaxを実装します。

  1. ブラウザが、非同期通信のHTTPリクエストをサーバーに送る。
  2. 非同期通信のHTTPリクエストを受け取ったサーバーが、HTTPリクエストの内容を処理する。その後、JavaScriptをHTTPレスポンスとして返す。
  3. HTTPレスポンスを受け取ったブラウザが、受け取ったJavaScriptを使って、DOMを操作する。DOMを操作することで画面の表示が変化する。
  4. 終了

つまり、非同期通信なのに画面が変化する現象が起こる理由は、JavaScriptを使ってDOMを操作しているからです。 DOMを操作することで画面を構成しているノードオブジェクトが変化します。そのため、Webページが変化します。また、先ほどJavaScriptをHTTPレスポンスとして返すと言いましたが、正確に言うと、非同期通信のHTTPリクエストの場合、コントローラのアクション終了後にアクション名.js.erbというファイルをブラウザに返しています。 js.erbはRubyが書けるjsファイルなので、アクションで生成したインスタンス変数をこのファイルに書くことができます。つまり、JavaScriptとDOMを使って、インスタンス変数の内容を画面に表示できます。

6.実際にRailsでAjaxを実装する

今回はRailsでAjaxを実装することで、画面遷移なしでコメントの追加と削除ができるようにします。
以下の手順で実装していきます。

  1. controllers/comments_controller.rbのcreateアクションとdestroyアクションに以下のコードを書きます。
controllers/comments_controller.rb(一部抜粋)
  def create
    @comment = current_user.comments.build(comment_params)
    @comment.board_id = params[:board_id]
    @comment.save
  end
  def destroy
    @comment = current_user.comments.find(params[:id])
    @comment.delete
  end
  1. ルーティングを作成します。その際、Commentsコントローラのcreateアクションやdestroyアクションで掲示板のidを使用しないので、shallowオプションを設定します。
config/routes.rb(一部抜粋)
  resources :boards do
    resources :comments, only: %i[create destroy], shallow: true
  end
  1. form_withヘルパーはデフォルトで非同期通信をするので、コメント作成フォームのlocal: trueを消します。あと、非同期通信終了後はフォームの入力欄を空にしたいので、js-formをフォーム要素に付与します。このようにすることで、JavaScriptとDOMを使ってフォーム要素を操作できます。
views/comments/_form.html.erb
  <div class="row mb-3">
    <div class="col-lg-8 offset-lg-2">
      <%= form_with model: [board, board.comments.build], class: 'js-form' do |f| %>
        <div class: 'form-group'>
          <%= f.label :body %>
          <%= f.text_area :body, row: 4, class: 'form-control mb-3', id: 'comment_body' %>
        </div>
        <%= f.submit I18n.t("comments.create.post"), class: 'btn btn-primary' %>
      <% end %>
     </div>
  </div>
  1. コメント削除ボタンはデフォルトで同期通信になっているので、remoteオプションを設定します。remote: trueに設定すると、非同期通信になります。
views/comments/_comment.html.erb(一部抜粋)
  <%= link_to comment_path(comment.id), class: "js-delete-comment-button", method: :delete, remote: true do %>
    <i class="fa fa-trash"></i>
  <% end %>
  1. _comment.html.erbのtable要素とtr要素にidを付与します。
views/comments/_comment.html.erb
  <div class="row"> 
    <div class="col-lg-8 offset-lg-2">
      <table id="js-table-comment-<%= comment.id %>" class="table">
        <tr id="comment-<%= comment.id %>">
          <td style="width: 60px">
            <%= image_tag 'board_placeholder.png', class: "rounded-circle", width: '50', height: '50' %>
          </td>
          <td>
            <h3 class="small"><%= comment.user.decorate.full_name %></h3>
            <div id="js-<%= comment.id %>">
              <p><%= simple_format(comment.body) %></p>
            </div>
            <div style="display: none;">
              <textarea class="form-control mb-1">コメントです</textarea>
              <button class="btn btn-light">キャンセル</button>
              <button class="btn btn-success">更新</button>
            </div>
          </td>
          <% if comment.decorate.is_mine?(current_user) %> 
            <td class="action">
              <ul class="list-inline justify-content-center" style="float: right;">
                <li class="list-inline-item">
                  <%= link_to '#', class: "js-edit-comment-button" do %>
                    <i class="fas fa-pen" ></i>
                  <% end %>
                </li>
                <li class="list-inline-item">
                  <%= link_to comment_path(comment.id), class: "js-delete-comment-button", method: :delete, remote: true do %>
                    <i class="fa fa-trash"></i>
                  <% end %>
                </li>
              </ul>
            </td>
          <% end %>
        </tr>
      </table>
    </div>
  </div>
  1. boards/show.html.erbのコメントエリアとエラーメッセージエリアにidを付与します。
boards/show.html.erb(一部抜粋)
  <!-- エラーメッセージエリア -->
  <div class="js-message-errors container"></div>
  <!-- コメントフォーム -->
  <%= render partial: "comments/form", locals: { board: @board } %>

  <!-- コメントエリア -->
  <div id="js-comment-area">
    <%= render @comments %>
  </div>
  1. create.js.erbを作成します。
views/comments/create.js.erb
  $('.js-message-errors').empty(); #指定した要素の子要素を全て削除する。つまり、もしエラーメッセージが画面に表示されてたら、そのエラーメッセージは消える。 
  <% if @comment.errors.present? %>
    $('.js-message-errors').prepend("<%= j(render 'shared/validation_errors', object: @comment) %>");
  <% else %>
    $('#js-comment-area').prepend("<%= j(render 'comments/comment', comment: @comment ) %>");
    $('.js-form')[0].reset(); #フォームリセットに使う。jQueryでresetメソッド使いたいなら[0]が必要。
  <% end %>
  1. destory.js.erbを作成します。
views/comments/destory.js.erb
  document.querySelector('#js-table-comment-<%= @comment.id %>').remove(); #指定した要素を消す。
  1. 終了

ブラウザを確認してみると、画面遷移なしでコメントの追加と削除が実装できてます。
Image from Gyazo

7.終わり

Railsの場合、Ajaxが簡単に実装できるので効率良いですね。

この記事が少しでも役に立てば幸いです!

8.参考文献

https://pikawaka.com/rails/remote-true
https://zenn.dev/swata_dev/articles/c51f3b781c916d
https://www.bokukoko.info/entry/2017/12/22/194327
https://applingo.tokyo/article/654
https://books.google.co.jp/books/about/独習Ruby_on_Rails.html?id=XNydDwAAQBAJ&redir_esc=y
https://books.google.co.jp/books/about/現場で使えるRuby_on_Rails_5_速習.html?id=bP46vQEACAAJ&redir_esc=y

Discussion

ログインするとコメントできます