強欲で謙虚なツボツボ

趣味の読書の書の方

【Rails】form_withで非同期通信して表示を動的に変更(data-remote="true")

概要

リストを表示するときに追加読み込み、並び替え、絞り込みなどで表示を動的に変更したいことがしばしばある。
そんな時に、:remote => true.js.erbなどを利用して対応する。
また、そのままだと:remote=>trueをしてもJSリクエストとして処理されず、.js.erbではなく.html.erbが描画されてしまったのでrails-ujsを使うようにする。

 

環境

ruby 3.1.2p20 (2022-04-12 revision 4491bb740a)
Rails 7.0.3.1
Bundler version 2.3.7

 

完成物

github

https://github.com/di-kotobuki/hatena/tree/ajax

・動作の様子

 

data-remote="true"

フォームやリンクのhtml要素にdata-remote="true"の属性が付くと通信がJSリクエストとなって非同期通信になる。
これの何がいいかと言うと、画面がリロードされずに画面の一部の要素を再描画できたりすることだと思う。
VueやReactを使うなら不要になる。

 

やり方

link_toであればremote: trueを、form_withであればlocal: falseを設定すればいい。

Rails で JavaScript を利用する - Railsガイド

route

適当なgetとpostのルートを作成。

Rails.application.routes.draw do
  get "/articles", to: "articles#index"
  post "/articles", to: "articles#create"
end

controller

上記で参照しているarticlesコントローラーとそのメソッドを作成。
コマンド一つでcontrollerやviewを作ってくれるのでそれを編集していく。

$ rails generate controller Articles index --skip-routes
class ArticlesController < ApplicationController
  def index
    @articles = ["aaa", "bbb", "ccc"]
  end

  def create
    @article = params[:title]
    respond_to do |format|
      format.html { redirect_to articles_path }
      format.js
    end
  end
end

view

indexでレンダリングされるindex.html.erbは先のコマンド実行で作成されているので適当に加筆。

<Articles#index>
<Find me in app/views/articles/index.html.erb>
<div>
  <%= form_with url: articles_path, local: false do |f| %>
    <%= f.text_field :title %>
    <%= f.submit %>
  <% end %>
  <%= render "list", articles: @articles %>
</div>

リスト部分をテンプレートに分割する(_list.html.erb)。

<ul id="articles">
  <% articles.each do |article| %>
    <li><%= article %></li>
  <% end %>
</ul>

createでレンダリングされるのはcontroller側で特に指定していなければcreate.js.erbレンダリングされる。これは自分でファイルを作成する。
内容は、formで入力された値でhtml要素を追加するというもの。

(() => {
  document.getElementById("title").value = "";
  const target = document.getElementById("articles");
  const el = document.createElement("li");
  el.textContent = "<%= @article %>";
  target.appendChild(el);
})();

 

rails-ujs

ここまでの状態でrails -sで起動して確認してみると、submitしても何もページが再読み込みされるだけで何も起こらない。
本来formt.jscreate.js.erbが読み込まれるはずが、format.htmlindexへのリダイレクトになってしまっている。
この理由は、submit時のリクエストのContent-TypeでMIMEタイプがtext/htmlになっているからで、これはrails-ujsを適用すれば解決するらしい。
この辺りの通信に関する部分はRailsのバージョンで異なっていたりするらしいので、調べるときはRailsのバージョンをちゃんと見た方がいい。
下記を参考にする。

javascript - Rails UJS not firing with Rails 7 - Stack Overflow

プロジェクトルートで実行。

$ ./bin/importmap pin @rails/ujs

app/javascript/application.jsに追記。

import Rails from '@rails/ujs';
Rails.start();

 

おしまい

これで完成物の出来上がり。