非同期通信
非同期通信を学ぶために、簡単なToDoアプリを作成する。
まずはそのアプリケーションの準備をする。
作成するアプリケーションを把握
今回作成するアプリケーションは以下の画像のようなTodoアプリ。
これまで作成してきたRuby on Railsのアプリケーションと異なる点は、Todoの保存を非同期通信で行っている点である。
アプリケーションをクローンする
作成するアプリケーションの雛形を以下のコマンドを実行してクローンなどの準備をする。
1 2 3 4 5 6 7 |
bundle installでエラーが生じた場合は以下のコマンドを実行し、再度bundle installを実行
1 |
$ bundle config --global build.mysql2 --with-opt-dir="$(brew --prefix openssl)"
|
memo_appをエディタで開く
クローンしてきたmemo_app
では、前もってモデル(todo.rb)とコントローラ(todos_controller.rb)とビューファイル(todos/index.html.erb)が用意されている。
Todoを保存できるようにする
投稿されたTodoを保存するように実装する。保存後には、Todoの一覧ページにリダイレクトさせる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class TodosController < ApplicationController
def index
@todo = Todo.new
@todos = Todo.order('created_at ASC')
end
def create
Todo.create(todo_params)
redirect_to :root
end
private
def todo_params
params.require(:todo).permit(:content)
end
end
|
解説
indexアクション
indexアクションで返すビューはTodo一覧のページとTodoの入力ページを兼ねているため、ページに必要なのはテーブルに保存されているTodo全てと、新しいTodoを作成するためのTodoクラスのインスタンスである。それぞれを生成し、インスタンス変数に代入する。
createアクション
テーブルにデータを保存した後、ルートパスにリダイレクトする。
動作を確認
以下のように投稿ができることが確認できれば成功。
非同期通信
非同期通信について
画面をリロードしなくても情報が反映されるWebアプリケーションの機能。例えば、以下のChatSpaceサンプルなどがそうである。メッセージを投稿すると、ブラウザの再読み込みがされることが無く、データが更新されることがわかる。
ブラウザのリロードボタンに注目すると、ブラウザが再読み込みされているかどうかがわかる。
一方、作成中のMemoAppは投稿する度にブラウザがリロードされてしまう。
非同期通信(Ajax)
リクエスト後にレスポンスが帰ってきた際、ブラウザが再読み込みされること無く通信が行われる通信方法。非同期通信は英語で"Asynchronous JavaScript + XML"と表現され、略してAjax(エイジャックス)と呼ばれる。
Ajaxでは、レスポンスのデータにJSONという形式が使われることが多い。
JSON
Java Script Object Notationの略で、データ交換を行うためのデータ記述形式の一種。Rubyのハッシュと同様、キーとバリューの組み合わせでデータを表現する形式。
1 |
{user_name: "testさん", created_at: "2019-08-28T10:35:13.000+09:00", content: "これがJSONの形です!", image_url: null, id: 6}
|
これまでのアプリケーションのレスポンスにはHTMLが用いられてきた。HTMLをレスポンスとして受け取ると、ブラウザは全てのHTMLを書き換える。そのため、画面が再読み込みされる。
一方、Ajaxでは主にJSONという型でレスポンスが行われる。この時ブラウザではJavaScriptが動作し、JavaScriptはサーバからJSON形式で返されたデータをHTMLに成形、ブラウザを書き換える。この仕組みにより、ブラウザの一部だけを更新することが可能になる。
非同期通信の実装ポイントを確認する
実際にコードを書いていく前に、非同期通信の実装ポイントがある。
これまでの同期通信の処理の流れと比較する形で見ていく。
ポイント① 非同期通信ではJavaScriptを利用してリクエストを行う
まずはこれまでの通信方法である同期通信について、
以下の図を見ると、
同期通信では、フォームのinputタイプがsubmitであるボタンを押すことでリクエストを行うことができた。ボタンを押すとリクエストが送られるという挙動は、HTMLであらかじめ定められているもの。こうした動作のことを、デフォルトアクションと呼ぶ。
デフォルトアクション
HTMLの要素を操作した際に定められている挙動。先に挙げたフォームのリクエスト送信以外だと、例えばaタグにもデフォルトアクションがある。クリックされると、リンク先のページを開く、という挙動である。
対して、非同期通信では以下の図のようにJavaScriptのメソッドを利用してリクエストを送る。そのため、フォーム要素のデフォルトアクションを無効にする必要がある。
またこの時、リクエストに対してのレスポンスはJSON形式で返してほしい旨をリクエストに含める。
ポイント② コントローラでJSON形式のデータを用意するよう準備
同期通信の際は特に指定せずともHTML形式のデータを返すようRailsが動いてくれる。
非同期通信をする際は、リクエストにJSON形式で返してほしい旨の情報が含まれているため、その場合の対処をコントローラのアクションに明記する必要がある。
ポイント③ レスポンスするためのJSON形式のデータを準備
同期通信ではviewsディレクトリの中に○○.html.erbという形式でHTMLのファイルを準備して置くことでレスポンスとしてHTMLを返すことができた。
非同期通信の場合、JSONのデータをレスポンスとして返す必要がある。短いデータであればコントローラ内に直接書けるが、同期通信の際と同様viewsフォルダの中にJSON形式のファイルを準備することもできる。この時のファイル名は、○○.json.jbuilderという形式になる。
ポイント④ JavaScriptでレスポンスを受け取り、HTMLを操作してToDoを追加
同期通信におけるレスポンスはHTML形式であり、受け取ったHTMLを描画するためにブラウザは一度リロードされる。
対して非同期通信では、ページがリロードされることはない。代わりに、レスポンスとして帰ってきたJSONのデータを利用してHTMLを操作する。
JSONのデータはユーザーが投稿したToDoのデータなので、これをToDoリストの一番後ろに付け加えるようJavaScriptを書く。
非同期通信の実装
Railsには、1つのコントローラのアクションの中でHTMLとJSONといったフォーマット毎に条件分岐できる仕組みがある。
respond_to
Railsのコントローラーで利用できる respond_to
というメソッドは、「リクエストがHTMLのレスポンス求めているのか、それともJSONのレスポンス求めているのか」を条件に条件分岐してくれる。
1 2 3 4 |
HTMLを返す場合は、該当するビューを呼びその中でデータを生成していたが、JSONを返す場合はRubyのハッシュをrender
メソッドの引数として渡すだけでJSONに変換される。そのため、以下のようにコントローラーから直接データを返すことができる。こちらは、renderというメソッドに{json: { id: @user.id, name: @user.name }}
というハッシュを引数として渡している。
1 2 3 4 5 |
JSONでレスポンスできるようにする
1 2 3 4 5 6 7 |
解説
respond_to do
を使用し、リクエストされたformatによって処理を分けるようにする。html
と非同期通信のためのjson
を扱うように記述した。フォーマットがjson
の時の説明をする。この後jsファイル側で作成したtodo(@todo)を使用するためにrenderメソッド
を使用し、作成したtodoをjson形式で返すようにする。
JavaScriptを記載する準備をする
jQueryを記述しながらAjaxによる非同期通信の実現を目指す。Ruby on Rails内では、JavaScriptファイルをassets/javascripts
以下に作成する。
今回は、todo.js
というファイルをassets/javascripts
以下に作成。
JavaScriptを記載して送信時に要素を取得
Ruby on Railsでは、アプリケーション作成時にjquery-rails
というgemをインストールし、assetsディレクトリ以下のapplicaton.js
ファイルで//= require jquery
このように記述することでjQueryを読み込んでいる。そのため、jQueryを読み込むための記述をこちらがする必要はない。
先ほど作成したtodo.jsにTodoの一覧ページからフォームが送信された時に、フォームに入力された値をコンソールに出力するという記述を行う。Ruby on Railsのアプリケーションの中だからといって書き方は変わらない。
1 2 3 4 5 6 7 |
$(function() {
$('.js-form').on('submit', function(e) {
e.preventDefault();
var todo = $('.js-form__text-field').val();
console.log(todo);
});
});
|
解説
submit
イベントを使い、フォーム(js-form)が送信された時に処理が実行されるようにイベントを設定する。
フォームが送信された時に、デフォルトだとフォームを送信するための通信がされてしまうので、preventDefault()
を使用してデフォルトのイベントを止める。
本カリキュラムとChatSpaceの解答例は、変数宣言にlet
やconst
ではなく、ES5の記法であるvar
を使用している。
以下の画像のようになっていれば問題ない。
非同期通信でリクエストする
Ajaxを利用して、ToDoの保存を非同期で行えるようにする。
処理の流れ
- フォームの送信が行われた時にAjaxによる非同期通信を始める
- フォームに入力された値を取得する
- Ajaxを行う記述をする
- TodosコントローラのcreateアクションにてTodoの保存を行う
- 処理後にjsonを返す
- 非同期通信の終了後に受け取ったjsonを利用してHTMLを構築する
- 4で構築したHTMLをViewに差し込む
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
5行目の$.ajax
という部分が新たに登場する記述である。
$.ajax
jQueryで非同期通信を行うための記述。.ajax
の部分がメソッドの呼び出しとなっている。
7〜14行目は引数。ajaxメソッドには、Rubyのハッシュのようなキーバリュー形式で引数を渡す。
JavaScriptでは、このような形式のオブジェクトはオブジェクト型と呼ばれる。読みやすいように改行して縦に並べているが、以下のようなキーとバリューの組み合わせである。
{type: 'POST', url: '/todos.json', data: { todo: { content: todo } }, dataType: 'json' }
少し複雑なのは、オブジェクト型の入れ子になっているdata以降の記述である。data
というキーに対してのバリューはオブジェクト型であり、さらにそのバリューもオブジェクト型になるという、入れ子構造となっている。
これらはAjaxで非同期通信に必要なオプションを設定しており、それぞれの意味は以下のようになる。
オプション | 説明 |
---|---|
type | HTTP通信の種類を記述する。通信方法は、GETとPOSTの2種類がある。 |
url | リクエストを送信する先のURLを記述する。 |
data | サーバに送信する値を記述する。 |
dataType | サーバから返されるデータの型を指定する。 |
これで、ブラウザはサーバに対してリクエストを送れる。改めて
5 ~ 14行目を読んでみると、以下のような意味になる。
- 通信方法は
POST
で、'/todos.json'というURLに{ todo: { content: todo(テキストフィールドに入力された値)} }
を送信する。サーバから値を返す際は、json
になる。
先ほどの続きから、以下の15行目以降の記述を行う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
$(function() {
$('.js-form').on('submit', function(e) {
e.preventDefault();
var todo = $('.js-form__text-field').val();
$.ajax({
type: 'POST',
url: '/todos.json',
data: {
todo: {
content: todo
}
},
dataType: 'json'
})
.done(function(data) {
var html = $('<li class="todo">').append(data.content);
$('.todos').append(html);
$('.js-form__text-field').val('');
})
.fail(function() {
alert('error');
});
});
});
|
文法について。
doneとfail
ajaxメソッドの後につけることで、非同期通信が成功した際/失敗した際に行う処理を書くことができる。両方とも、ajaxメソッドとセットとなるメソッド。doneは通信が成功したときに、failは通信が失敗したときに動く。
.doneのあとにあるdataという変数について
doneのうしろにあるdataという変数には、リクエストによって返ってきたレスポンスが代入される。この場合のレスポンスは、非同期通信によって作成したTodoにあたる。以下のように、フォーマットがjsonの場合は、作成したTodoをjson形式にして返す記述をしていた。
1 2 3 4 5 6 7 |
doneにもfailにも、メソッドの引数としてやってほしい処理を記述する。
今回はdoneのあとに、取得したJSONからli要素を作成しtodo一覧のリストに追加し、フォームに入力された値を空にする処理を記述している。failのあとにはエラーが起きたことを示すアラートを表示する処理を記述している。
うまく動くか確認
以下のように、非同期投稿ができることが確認できれば成功。
現状のコードでは、一度投稿すると投稿ボタンが非活化し、連続での非同期投稿はできない。こちらは仕様であり、一旦このままで続ける。
最後に、htmlを生成するvar html = $('<li class="todo">').append(todo.content);
という処理をメソッドにして切り出してみる。メソッドにして処理を分けることで、長くなりがちな処理を整理し、理解しやすくする効果がある。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
$(function() {
function buildHTML(todo) {
var html = $('<li class="todo">').append(todo.content);
return html;
}
$('.js-form').on('submit', function(e) {
e.preventDefault();
var todo = $('.js-form__text-field').val();
$.ajax({
type: 'POST',
url: '/todos.json',
data: {
todo: {
content: todo
}
},
dataType: 'json'
})
.done(function(data) {
var html = buildHTML(data);
$('.todos').append(html);
$('.js-form__text-field').val('');
})
.fail(function() {
alert('error');
});
});
});
|
これで、jQueryの実装は完了!
再度、うまく動くか確認
ブラウザをリロードし、問題なく動作することを確認する。
処理に失敗した際のアラートが出るか確認
以下の様に、todos_controller.rbにおけるjsonについての記述をコメントアウトすると、jsonの処理において不具合を仕込むことができるはず。
1 2 3 4 5 6 7 |
それでは実際にブラウザをリロードし、投稿を行ってみると、エラーのアラートが出現することがわかるはず。
アラートが確認できたら、コメントアウトは外しておく。