Railsで非同期通信を実装するまで

#WHAT
RailsjQueryを使ってアクションを非同期化する

環境
Ruby 2.3.1
Rails 5.1.6
haml 5.0.4
jQuery-rails 4.3.3

記事にしてる内容
RailsjQueryを使ってCRUD操作を非同期化するまでの作業手順。
非同期通信やajaxとは?という部分については詳しく説明しない。
一言で言えばページ読み込みを発生させず、1画面で完結するようなアプリや
googleマップのようなページの一部だけを更新するための技術である。
実装のためにJavaScriptが必須で、コードを簡単にするために
jQueryを併用して実装する例が多い。

大まかな作業手順
一般的に非同期通信を実装するには以下のような手順が必要である。

1.通常のリクエスト(同期通信)を止めて非同期のリクエストを出す
2.サーバーからも非同期で描画するための形式(json)でデータを返す
3.受け取ったデータをJavaScriptで描画する

ここからRailsでの具体的な実装。
例ではpostsコントローラーのnewアクションを非同期化するとして説明する。
また、ビューはhamlの記法で書いているがerbでも記法以外の違いは無い。


1.通常のリクエストを止めて非同期のリクエストを出す

リクエストの話なのでいじるのはビューファイル。
ここではviews/posts/index.html.haml

~ 省略 ~
= link_to  ~, remote: true
~ 省略 ~

リクエストを出しているlink_toやform_forに remote: true というオプションを追加。
form_withを使っている場合はデフォルトで remote: true となっている・・
というか普通のリクエストも処理できているので両対応。form_withすごい。

※この作業はjQueryでいう$.getや$.ajaxメソッドを使って
 HTTPリクエストを投げている部分に相当する。



2.サーバーからも非同期で描画するための形式でデータを返す

この点に関して、1.の手順でリクエストを出した場合必要な作業は無い。
本来は「同期通信ならhtml,非同期通信ならjsonで返す」
ということをコントローラに書く必要があるが、remote: true で送られた場合
自動的にjsonを返してくるようになる。Railsすごい

※この作業(無いけど)はコントローラのアクションにrespond_toを書き、
 返すformatを指定する部分に相当する。



3.受け取ったデータをJavaScriptで描画する

これは本来読み込んでいるJSファイルに書く記述だが、Railsではビューファイルとして
アクション名.js.erb や アクション名.js.haml を置いてやることで
サーバーからjsonが返ってきた場合にこのファイルを参照するようになる。
今回はnewアクションなので、views/posts/ に new.js.haml を作成する。

app
- views
  - posts
    - new.js.haml (新規作成)

中身には以下のように記述する。

$('#post_form').html("#{escape_javascript(render 'form')}");

セレクタ$('#post_form')はこの後作成する、非同期処理を描画するための空のdivである。

escape_javascriptRailsのメソッドだと思う(思う)
renderの結果を有効なJavaScriptの命令文として整形しつつ、
整形結果が本当に有効なJavaScriptになったかの検証もしているらしい。
参考→https://code.i-harness.com/ja/q/18b891

render 'form' 部分は通常のrenderメソッドのように部分テンプレートを呼び出すメソッドである。
ただしpartial:と書いたりlocal:を加えようとしたらエラーになった(検証不足)
ファイル名を単数形(form)とし、同名複数形のインスタンス変数(@forms)を使っている場合、
部分テンプレート内ではformという変数を使うことができる(collection型と同じ挙動)
そうでない場合(単数形のインスタンス変数@postなど)、ビュー内でも@postの形のまま使う。
ここでは新規投稿フォームを _form.html.erb としてこの後作成し、呼び出すことにした。

※この作業はjQueryのdoneや以下でbuildHtml関数を呼び出し、appendメソッドなどで
 追加するコードの部分に相当する。



id="post_form" へ描画する準備ができたので、元のビュー index.html.haml
実際にid="post_form"のdivを作っておく

%div#post_form

分かりやすさのために%divを書いたが、もちろん省略していい。


最後に、描画する中身となる部分テンプレート _form.html.haml を用意する。

app
- views
  - posts
    - _form.html.haml (新規作成)

中身には描画したいビューを普通に書けば良い。
以下は投稿画面の一例。
コピペしてもCSSが当たらないので各自で作ってください

.post-form
  = form_with model: @post, id: "new_post" do |f|
    .msr_text_03
      = f.label :title, value: "タイトル(必須)"
      = f.text_field :title, class: 'post-title'
    .msr_file_03
      %p 画像
      = f.label :image, for: "hidden-file" do
        %i.material-icons add_a_photo
        = f.file_field :image, id: "hidden-file"
    .msr_textarea_03
      = f.label :body, value: "本文(必須)"
      = f.text_area :body, class: 'post-body'
    %p.msr_sendbtn_03
      = f.submit value: "Send"

※この作業はjQueryでいうとbuildHtmlを定義している部分に相当する