公開日: | 最終更新日:
【Rails】 remote: trueでフォーム送信をAjax実装する方法とは?
form系のヘルパーメソッドには、:remoteオプションがあります。これをtrueに設定(remote: true)することで、 Ajaxを簡単に実装する事が出来ます。
Ajaxを実装すると、以下の動画のように画面遷移することなく、データを保存してページの一部だけ更新することが出来ます。
Railsでは、フォーム系ヘルパーなどにremote: true
を設定することで、簡単に実装することが出来ます。
Ajaxとは、JavaScriptでサーバー側との通信を「非同期」で行い、通信結果によって「動的にページの一部だけ書き換える手法のこと」です。
そもそも「Ajaxって何だろう?」という方や「Ajaxの仕組み」について理解出来ていない方は、こちらの「Ajaxとは?初心者向けに仕組みを徹底解説!」を読んでからAjax実装に入りましょう。
フォーム送信をAjaxで実装する方法
この章では、remote: true
を使用した場合のAjaxの実装方法を知りたい人・仕組みを理解したい人に向けて、フォーム送信をAjaxに実装しながら1つ1つ解説します。
以下の動画のように、実装前(右側)は画面遷移して更新していましたが、Ajaxを実装(左側)すると、画面遷移なしで登録したメッセージを追加する事が出来ます。
(左側)Ajax実装済み / (右側) Ajax未実装
Ajaxを実装する流れは、以下の通りです。
- 前準備:アプリケーション作成する
- フォームヘルパーにremote: trueを設定する
- リクエストされるフォーマットで処理を分ける
- 「〇〇.js.erbファイル」を作成する
- jsファイルに更新処理を記述する
remote: true
を使った事がない人は、この機会に是非アプリケーションを作成しながら進めてみてください。アプリケーションを作成する事で、より一層理解を深める事が出来ます
前準備:アプリケーション作成する
Ajaxを実装する前に、メッセージを投稿することが出来る簡単なアプリケーションを用意します。まずは、以下のコードを実行してrails newでsample_app
アプリケーションを作成します。
1
rails _5.2.1_ new sample_app -d mysql
次に、以下のコードのscaffold
でMessageモデルなど投稿メッセージを管理する為の雛形を作成します。
1
2
3
cd sample_app # ディレクトリを移動
rails g scaffold Message body:string # scaffoldを実行
# rails g scaffold モデル名 カラム名:型
そして、データベースの作成とscaffold
の内容を元にして作成されたマイグレーションを実行します。
1
bundle exec rails db:create && bundle exec rails db:migrate
ここまで実行したら、rails s
でサーバーを起動させてlocalhost:3000/messagesにアクセスすると、以下の動画のようにメッセージの登録・一覧表示・編集など簡単な機能が一通り作成されています。
Ajaxはまだ実装していない状態なので、上記の動画のようにメッセージを登録したり、メッセージの一覧を表示する際に画面遷移しています。これにremote: true
を使ってAjaxを実装する事で、画面遷移なしで登録したメッセージを表示出来るようにします。
1.フォームヘルパーにremote: trueを設定する
以下の画像のように、一覧画面に入力フォームと登録したメッセージを表示させます。
まずは、以下のコマンドでapp/views/messages/
配下に_message.html.erb
を作成し、コードを記述します。
1
touch app/views/messages/_message.html.erb
1
2
3
<li class="message"><%= message.body %></li>
<%# messageには、呼び出し側の@messagesの中身が1つ1つ渡される%>
<%# @messagesが全て渡されるまで、このテンプレートは繰り返し呼ばれる %>
上記の部分テンプレートは、この後に記述するindex.html.erb
の<%= render @messages %>
で呼び出されます。
そして、messages_controller.rbのindexアクション
、index.html.erb
をそれぞれ以下のコードに書き換えます。
1
2
3
4
5
6
class MessagesController < ApplicationController
def index
@messages = Message.all
@message = Message.new # 追加
end
end
1
2
3
4
5
6
7
8
9
10
11
<h1>Messages</h1>
<div class="contents">
<%= form_with(model: @message, class: 'js-form') do |f| %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
<ul class="messages">
<%= render @messages %> <%# _message.html.erbを呼び出す %>
</ul>
</div>
index.html.erb
では、簡単に入力フォームを作成してくれるform_withのヘルパーメソッドを使用します。form_withは、デフォルトで非同期通信をするように設定されているので、remote: true
を記述する必要はありません。
以下のコンパイル後のコードでは、formタグにdata-remote="true"
が追加されます。
1
2
3
<form class="js-form" action="/messages" accept-charset="UTF-8" data-remote="true" method="post">
<!-- 省略 -->
</form>
localhost:3000/messagesにアクセスして検証をすると、以下のようにformタグにdata-remote="true"
が追加されているのが分かります。
これにより、フォームを送信する際にHTML形式ではなくJS形式で送信する事ができます。
リクエストされる形式を調べる
リクエストされる形式を調べるには、request.format
を実行します。
まずは、以下のようにMessagesControllerのcreateアクションにbinding.pryでブレークポイントを記述します。(gemのインストール方法は、こちらの記事を参考にしてください。)
1
2
3
4
5
6
7
class MessagesController < ApplicationController
def create
binding.pry # 追加
@message = Message.new(message_params)
# 省略
end
end
そして、以下の動画のようにフォーム送信すると、プログラムの実行が停止されPryが起動します。そこでrequest.fomat
を実行するとリクストされた形式を調べる事が出来ます。
以下のように、request.format
の結果にapplication/javascript
とあるように、form_withで作成したフォームのリクエストがJS形式で送信されていることが分かります。
1
2
3
4
[1] pry(#<MessagesController>)> request.format
=> #<Mime::Type:0x00007f91a4ad6730 @hash=-969871837971611020,
@string="text/javascript", @symbol=:js,
@synonyms=["application/javascript", "application/x-javascript"]>
一方で、コンパイル後のformタグにdata-remote="true"
がない場合は、リクエストがHTML形式で送信されます。
form_withでは、以下のようにlocal: true
を追加することでdata-remote="true"
を無くして、HTML形式でリクエスト送信することが出来ます。
1
2
<%= form_with(model: @message, class: 'js-form' local: true) do |f| %>
<% end %>
localhost:3000/messagesにアクセスして検証をすると、以下のようにformタグにdata-remote="true"
が無くなっている事が分かります。
そして、以下の動画のようにフォーム送信すると、プログラムの実行が停止されPryが起動するので、request.fomatを実行します。
先ほどはapplication/javascript
でしたが、今度はapplication/xhtml+xml
とあるように、以下の結果からリクエストされた形式がHTMLで送信されていることが分かります。
1
2
3
[1] pry(#<MessagesController>)> request.format
=> #<Mime::Type:0x00007f91a4ad6d70 @hash=-2181586491714069490,
@string="text/html", @symbol=:html, @synonyms=["application/xhtml+xml"]>
form_forメソッドを使用する場合
form_forメソッドにremote: true
を設定しない場合は、以下のようにコンパイル後のコードにdata-remote="true"
は存在しません。
1
2
3
4
5
6
7
8
9
<%= form_for(@message) do |f| %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
<!-- コンパイル後のコード-->
<form class="new_message" id="new_message" action="/messages" accept-charset="UTF-8" method="post">
<!-- 省略-->
</form>
しかし、form_forメソッドにremote: true
を設定した場合には、以下のようにコンパイル後のコードにdata-remote="true"
が追加されます。
1
2
3
4
5
6
7
8
9
<%= form_for(@message, remote: true) do |f| %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
<!-- コンパイル後のコード-->
<form class="new_message" id="new_message" action="/messages" accept-charset="UTF-8" data-remote="true" method="post">
<!-- 省略-->
</form>
form_tagメソッドを使用する場合
form_tagメソッドを使用した場合も同様でremote: true
を設定しない場合は、以下のようにコンパイル後のコードにdata-remote="true"
は存在しません。
1
2
3
4
5
6
7
8
9
<%= form_tag('/messages', method: :post) do %>
<input type="text" name="message[body]">
<input type="submit">
<% end %>
<!-- コンパイル後のコード-->
<form action="/messages" accept-charset="UTF-8" method="post">
<!-- 省略-->
</form>
しかし、form_tagメソッドにremote: true
を設定した場合には、以下のようにコンパイル後のコードにdata-remote="true"
が追加されます。
1
2
3
4
5
6
7
8
9
<%= form_tag('/messages', method: :post, remote: true) do %>
<input type="text" name="message[body]">
<input type="submit">
<% end %>
<!-- コンパイル後のコード-->
<form action="/messages" accept-charset="UTF-8" data-remote="true" method="post">
<!-- 省略-->
</form>
2.リクエストされるフォーマットで処理を分ける
Railsのリクエスト送信〜レスポンスが返るまでの大枠の流れは、以下の通りです。
- リクエストを送信する
- ルーティングで受け取ったリクエストに対応するコントローラのアクションを割り当てる
- 割り当てられたコントローラのアクションが実行される
- アクションの処理終了後、アクション名と同じビューファイル(views/コントローラー名/アクション名.リクエスト形式.erb)が呼び出される
上記の「4. アクション名と同じビューファイルが呼び出される」は、以下の画像のようにリクエストされる形式によって変わります。
JS形式でリクエストを送信した場合は、アクション名.js.erb
、HTML形式でリクエストを送信した場合は、アクション名.html.erb
が呼び出されます。
しかし、上記の画像のようにリクエストの形式ごとに呼び出されるビューファイルが違っても、その前に実行されるアクション(createアクション)は同じです。
その為、リクエストの形式ごとに処理を分ける場合は、「respond_toメソッド」を使います。
JS形式とHTML形式で処理を分ける場合
今回は、respond_toメソッドでリクエストされるフォーマット(HTML形式/JS形式)ごとに処理を分けていきます。
respond_toメソッドは、以下のコードのようにformat.形式
で各形式の処理を記述することが出来ます。(詳細は、「respond_toメソッドの使い方」を参考にしてください。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MessagesController < ApplicationController
def create
@message = Message.new(message_params)
respond_to do |format|
if @message.save
format.html { redirect_to @message } # showアクションを実行し、詳細ページを表示
format.js # create.js.erbが呼び出される
else
format.html { render :new } # new.html.erbを表示
format.js { render :errors } # 一番最後に実装の解説あります
end
end
end
end
上記の処理を簡単に解説すると、if文でデータベースにメッセージの保存が成功すればHTML形式の場合は、showアクションを実行します。JS形式の場合は、create.js.erb
が呼び出されます。
そして、データベースの保存が失敗すればHTML形式の場合はnew.html.erb
を表示します。JS形式の場合はerrors.js.erb
が呼び出されます。(エラー実装のセクションで詳しく解説します。)
3.「〇〇.js.erbファイル」を作成する
メッセージの登録が成功すると、create.js.erb
が呼び出されますが、scaffoldではこのファイルは作成されないので、以下のコマンドを実行してファイルを作成しておきます。
1
touch app/views/messages/create.js.erb
create.js.erb
では、以下の3つのことが可能になります。
それでは、フォーム送信した際に本当にcreate.js.erbファイル
は呼び出されるのか、ファイル内に記述したJavaScriptのコードは実行されるのか確かめます。
以下のコードをcreate.js.erbファイル
に記述します。
1
console.log("create.js.erbファイルが呼び出されました!");
そして、以下の動画のようにフォーム送信して無事データが保存されるとcreate.js.erbファイル
が呼び出されてconsole.log()
の内容がコンソールに表示する事が出来ています。
また、createアクションで定義したインスタンス変数@message
も、以下のようにcreate.js.erbファイル
で使用する事が出来ます。
1
console.log("<%= @message.body %>");
コンソールでも以下のように表示されていますね。
〇〇.js.erbファイル
では、JavaScriptとインスタンス変数(ERB)を使えるので、画面遷移なしで登録したメッセージの一部だけ追加する処理は簡単に記述する事が出来ます。
4. jsファイルに更新処理を記述する
最後に登録したメッセージを画面遷移なしで、以下の画像のように既に登録済みのメッセージのリストに追加して表示していきます。
それでは、以下のコードをcreate.js.erb
に記述します。
1
$('.messages').append("<li class='message'><%= @message.body %></li>");
appendメソッド
は、引数に指定した要素をある親要素の末尾に追加してくれるメソッドです。上記では、messageクラスが付与された親要素に引数の子要素を追加します。
messagesクラスの親要素は、以下の画像で既存メッセージを表示するul要素だと分かりますね。
このulの閉じタグの前に<li class='message'><%= @message.body %></li>
が追加されます。@messageは、登録したメッセージの情報を保持しているので@message.body
とすることで、メッセージの内容を表示させています。
アプリケーションを動かしてみると、以下のように画面遷移なしで登録したメッセージを表示することが出来ています。
しかし、このままでは入力したメッセージが登録後も残っているので、いちいち消してから次のメッセージを入力しなければなりません。
メッセージを登録したら入力フォームの内容をリセットする為に、以下のコードを追加しておきましょう。
1
2
$('.messages').append("<li class='message'><%= @message.body %></li>");
$('.js-form')[0].reset(); // 追加
js-formクラス
は、form要素を指しています。resetメソッドを使うことで入力フォームの内容をリセットすることが出来ます。
resetメソッドについては、jQueryでresetメソッドを使う方法を参考にしてください。
以上がremote: true
を使ったフォーム送信のAjax実装方法です。ここまでのAjax実装の全体の流れを以下の画像で一度整理してみましょう。
今回は、form_withを使用したのでremote: true
は設定しませんでしたが、入力フォーム作成の際にform_forやform_tagを使用する場合は忘れないように気をつけてください。
Ajax実装のような少しレベルの高い機能を実装してみたい!という方は、こちらの参考書で学ぶことができます!
部分テンプレートを呼び出して更新する
〇〇.js.erbファイル
では、部分テンプレートを呼び出して、ページの一部を更新する事が出来ます。
例えば、先ほど実装したcreate.js.erbファイル
ですが、メッセージ一覧に新しく登録したメッセージを追加する為、以下のように記述しています。
1
$('.messages').append("<li class='message'><%= @message.body %></li>");
しかし、上記に記述するappendメソッドの引数の内容は、以下の_message.html.erb
と同じです。
1
<li class="message"><%= message.body %></li>
その為、appendメソッドの引数に_message.html.erb
の部分テンプレートを呼ぶことで、同じようにメッセージ一覧に新規メッセージを追加する事が出来ます。
部分テンプレートは、以下のように記述して呼び出します。
1
$('.messages').append("<%= escape_javascript(render partial: 'messages/message', locals: { message: @message }) %>");
escape_javascript
は、改行やエスケープ処理をしてくれます。これはj
というエイリアスを使用して、以下のように記述する事も出来ます。
1
$('.messages').append("<%= j(render partial: 'messages/message', locals: { message: @message }) %>");
appendメソッドの箇所を部分テンプレート呼び出しに変更しても、以下のようにメッセージ一覧に登録したメッセージを追加する事が出来ています。
このように、ページの一部を更新する際に部分テンプレートはよく使われるので覚えておきましょう!
非同期通信のエラー処理を実装する
メッセージの登録に失敗した場合は、以下の動画のように画面遷移なしでエラーメッセージを表示する事が出来ます。(メッセージが空の場合は、保存出来ないようにvalidationを追加しています。)
基本的にエラー処理はhtml形式でrenderメソッドを使用してrenderingすることが多いのですが、ここでは非同期通信のJS形式でエラー処理を実装してみましょう!
それでは、以下の手順でエラー処理を実装していきます。
- validationを追加する
- エラーメッセージの表示箇所を追加する
- 「errors.js.erbファイル」を作成する
- 更新処理を記述する
1.validationを追加する
まずは、メッセージが空で送信された場合に保存出来ないようにvalidationを追加します。
以下のコードをmessage.rb
のMessageモデルに記述します。
1
2
3
4
class Message < ApplicationRecord
# validates :カラム名, presence: true
validates :body, presence: true
end
メッセージの内容を保存するカラムは、body
なのでvalidates :body
とします。
そして、指定したbodyカラム
の「値が空ではない」ことをチェックしてくれるpresenceヘルパーを使用します。
2.エラーメッセージの表示箇所を追加する
次に、エラーメッセージを表示する箇所を追加します。
以下のように、index.html.erb
に<div class="js-message-errors"></div>
を追加します。
1
2
3
4
5
6
7
8
9
10
11
<h1>Messages</h1>
<div class='contents'>
<div class="js-message-errors"></div> <!-- 追加するコード -->
<%= form_with(model: @message, class: 'js-form') do |f| %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
<!-- 省略 -->
</div>
クラス名はjs-message-errors
となっていますが、このクラスのdiv要素に後ほどJavaScriptで処理を追加することで、エラーメッセージを表示させる事が出来ます。
つまり、上記に追加したコードはJavaScriptを使って操作する為の目印となります。
3.「errors.js.erbファイル」を作成する
以下のmessages_controller.rb
で、メッセージの保存が失敗したときの処理をもう一度確認します。メッセージの保存が失敗すると、以下のelse以降の処理が実行されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MessagesController < ApplicationController
def create
@message = Message.new(message_params)
respond_to do |format|
if @message.save
format.html { redirect_to @message }
format.js
else
# 保存が失敗した時に実行される処理
format.html { render :new }
format.js { render :errors } # errors.js.erbが呼び出される
end
end
end
end
また、リクエストはJS形式で送信されるので、format.js { render :errors }
が実行されてerrors.js.erb
が呼び出されます。
このerrors.js.erb
は、app/views/messages/
のファイルが呼び出されるので、以下のコマンドでファイルを作成します。
1
touch app/views/messages/errors.js.erb
4.更新処理を記述する
メッセージの保存が失敗した場合エラーメッセージが表示されるように、errors.js.erb
に以下のコードを記述します。
1
2
3
4
5
6
$('.js-message-errors').replaceWith(
"<%= j(
render 'layouts/validation_errors',
instance: @message
) %>"
);
上記のコードは、index.html.erbに追加した.js-message-errors
の要素をreplaceWith()
の引数で呼び出している部分テンプレートの内容に置き換えています。
部分テンプレートには、@messege
をinstance
として使えるように渡します。
部分テンプレート作成
この内容をもう少し理解する為に、以下のコマンドを実行して呼び出す部分テンプレート(_validation_errors.html.erb
)を先に作成します。
1
touch app/views/layouts/_validation_errors.html.erb
次に、_validation_errors.html.erb
にbinding.pryを記述して以下の動画ように空のメッセージを送信してみます。プログラム実行が停止したところでinstance
を調べると、空で送信したメッセージの情報(@message
)が格納されています。
1
<% binding.pry %>
また、データの保存が失敗すればエラー情報が追加されるので、上記のようにerrors.full_messagesを使って詳細なエラーメッセージを取得する事が出来ます。
したがって_validation_errors.html.erb
に、以下のコードを記述することでinstance
にエラーメッセージが存在すれば、そのエラーメッセージを表示させる事が出来ます。
1
2
3
4
5
6
7
8
9
10
11
<!--エラーメッセージが存在すれば処理を実行する-->
<% if instance.errors.full_messages.present? %>
<div class="validates js-message-errors">
<ul>
<!--エラーメッセージを全て表示させる-->
<% instance.errors.full_messages.each do |msg| %>
<li style="color: #bb4a4a;"><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
ここまで実装した後に空メッセージを送信すると、以下の動画のように一覧画面にエラーメッセージが表示されます。検証部分に注目してみてください。
元々あった.js-message-errors
の要素がreplaceWith()
の引数に渡した部分テンプレートの内容に置き換わっていますね。
しかし、以下のように正常にメッセージを入力して送信しても、前に送信した際に表示されたエラーメッセージがそのまま残ってしまっています。
正常にメッセージを入力して送信した場合は、このエラーメッセージは表示させたくないので以下のようにcreate.js.erb
にempty()
で.js-message-errors
内の要素を空にします。
1
2
3
4
5
6
7
8
$('.messages').append(
"<%= escape_javascript(
render partial: 'messages/message',
locals: { message: @message }
)%>"
);
$('.js-form')[0].reset();
$('.js-message-errors').empty(); // 追加
これでエラーメッセージが表示された後に、正常にメッセージ入力して送信してもエラーメッセージが表示されたままということもなくなります。
エラー処理の流れを整理する
ここまでのエラー処理の流れを一度整理しましょう。メッセージを空で送信した場合は以下の流れでエラーメッセージが表示されます。
.js-message-errors
の要素を部分テンプレートの内容に置き換えて、エラーメッセージを表示している事が分かりますね。そして、以下の画像で置き換えられた部分テンプレートの内容を良くみると、js-message-errors
のクラス名がついたdiv要素があります。
これは、部分テンプレート内で定義されています。以下のようにdiv要素にjs-message-errors
のクラスを付与しています。
1
2
3
4
5
<% if instance.errors.full_messages.present? %>
<!-- 以下のdiv要素にjs-message-errorsのクラスを付与する -->
<div class="validates js-message-errors">
<ul>
<!--省略-->
上記の部分テンプレートにこのクラスを付与していなければ、連続して空のメッセージが送信された場合は上手くエラーメッセージを表示する事が出来ません
なぜなら、1回目に空のメッセージを送信した時点で、replaceWith()
によって.js-message-errors
は部分テンプレートに置き換えられるので、2回目以降に空メッセージを送信しても部分テンプレートに置き換えるための.js-message-errors
が存在しないからです。
そのため、部分テンプレート内に.js-message-errors
のdiv要素を存在させる事で、2回目以降も空メッセージを送信してもエラーメッセージを表示させる事が出来ます。
今回は、remote: true
を使ってAjaxを実装しましたが、処理が複雑な場合はremote: true
だと実装が難しいので、JavaScriptで実装します。
こちらの実装や処理の流れを知りたい方は、「Ajaxチュートリアル(Rails + jQuery)~処理の流れを理解しよう!」を参考に実装に挑戦してみてください。
この記事のまとめ
- フォーム系ヘルパーに
remote: true
を使うと、JS形式でリクエストを送信する事が出来る - JS形式でリクエストが送信された場合は、
アクション名.js.erbファイル
が呼び出される アクション名.js.erbファイル
は、ERBも使えるので部分テンプレート呼び出しやインスタンス変数を使ってページを一部更新する事が出来る!