LoginSignup
4
3

More than 1 year has passed since last update.

Rails: フォームでdata-remote="true"を使うには

Last updated at Posted at 2021-09-15

フォームにdata-remote="true"を付けたときの挙動について、ちゃんと理解していなかったので、ここにメモします。

ここらへんの機能はRails 7でHotwire(Trubo)で置き換わるようなので、いずれ無用な知識になるかもしれません。

form_withとform_forのオプション

form_withにオプションlocal: falseを付けます。すると、HTMLのformにdata-remote="true"が付きます。

<%= form_with(model: @entry, local: false, id: 'entry-form') do |form| %>
<form id="entry-form" action="/entries" accept-charset="UTF-8" data-remote="true" method="post">

Rails 6.0以前では、デフォルトでdata-remote="true"が付いてしまい、これを防ぐには local: true を付ける必要がありました。この仕様は混乱の元だったと思います。Rails 6.1ではdata-remote="true" なしがデフォルトになりました。

なお、form_forに指定するときはremote: trueです。デフォルトは、6.0も6.1もfalseです。

<%= form_for(@entry, remote: true, html: { id: 'entry-form' }) do |form| %>

data-remote="true"で送信し、リダイレクションを返した場合

コントローラで成功時にリダイレクションを返すとします。

def create
  @entry = Entry.new(entry_params)
  if @entry.save
    redirect_to @entry, notice: "保存しました。"
  else
    render :new, status: :unprocessable_entity
  end
end

data-remote="true"のときは、次のレスポンスが返ります。HTTPステータスは200、Content-Typeはtext/javascriptです。このレスポンスを受け取ると、rails-ujsがJavaScriptを実行します(Turbolinks.visitでページ遷移します)。

Turbolinks.clearCache()
Turbolinks.visit("http://localhost:3000/entries/13", {"action":"replace"})

data-remote="true"+リダイレクションのときは、次の流れになります。

  • data-remote="true"のフォームを送信すると、rails-ujsはリクエストヘッダに「Accept: text/javascript, application/javascript, ...」を付けてAjaxで送信。
  • このとき、Railsでredirect_toを実行すると、ステータス302ではなく200で、上記のJavaScriptを送信する。
  • rails-ujsは、「Content-Type: text/javascript」のレスポンスを受け取ると、そのJavaScriptを実行する(RJS)。

困る点1: 失敗時の処理が必要

上記のコントローラでは、保存に失敗したときはHTMLを返しています。rails-ujsは、「text/javascript」以外が返ったときは自動的に何かしてくれるわけではありません。

困る点2: ajax:successイベントを処理しても遷移する

上記のサンプルでは、次のようにajax:successイベントを処理しても、rails-ujsがレスポンスのJavaScriptを実行してページ遷移してしまいます。

  let form = document.getElementById('entry-form');
  if(form) {
    form.addEventListener('ajax:success', (evt) => {
      // 独自の処理...
    });
  }

実際にフォームでdata-remote="true"を使うには

次の例は、私がフォームでdata-remote="true"を使うときのやり方です(これが正解というわけではありません)。

コントローラでは、createとupdateはAjax専用ということにして、JSONだけを返します。RJS(レスポンスのJavaScriptを自動実行)は避けます。

成功時はredirect_toを使わずに、遷移先のパスをJSONデータで返します。失敗時はエラーメッセージの配列を返します。

  def create
    @entry = Entry.new(entry_params)
    if @entry.save
      flash.notice = "作成しました。"
      render json: { location: entry_path(@entry) }, status: :created
    else
      render json: { errors: @entry.errors.full_messages },
        status: :unprocessable_entity
    end
 end

  def update
    @entry.assign_attributes(entry_params)
    if @entry.save
      flash.notice = "更新しました。"
      render json: { location: entry_path(@entry) }, status: :ok
    else
      render json: { errors: @entry.errors.full_messages },
        status: :unprocessable_entity
    end
  end

JavaScriptでは、rails-ujsのイベントajax:successajax:errorsを処理して、リダイレクトしたりエラーを表示したりします。

let form = document.getElementById('entry-form');
if(form) {
  form.addEventListener('ajax:success', (evt) => {
    let data = evt.detail[0];
    Turbolinks.visit(data.location);
  });
  form.addEventListener('ajax:error', (evt) => {
    if(evt.detail[2].status == 422) {
      let data = evt.detail[0];
      // data.errorsを使って何かする
    }
  });
}

次の例は、erbで出力したフォームをVue.jsで処理するものです。

<%= form_with(model: entry, local: false, id: 'entry-form',
      '@ajax:success' => 'formAjaxSuccess',
      '@ajax:error' => 'formAjaxError') do |form| %>
new Vue({
  el: "#entry-form",
  data: {
    return {
      errors: []
    }
  },
  methods: {
    formAjaxSuccess(e) {
      let data = e.detail[0]
      Turbolinks.visit(data.location)
    },
    formAjaxError(e) {
      if(e.detail[2].status == 422) {
        let data = e.detail[0]
        this.errors = data.errors
      }
    }
  }
});

以上です。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3