はてブ初投稿

このブログはプログラミング(主にHTML&CSSRubyRails、JS&jQuery)勉強中の自分が

・遭遇したエラー
・実装に苦労したもの
・その他ググって残しておきたいと思ったもの

あたりを残しておくためのブログです。
人に読まれることはあんまり想定しない雑な内容になるとは思いますが、
アウトプットの場として積極的に使っていこうと思います。

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を定義している部分に相当する

renderメソッド(Rails)

WHAT
renderメソッドについてまとめる

基本構文

render :option

・指定したRHTMLを返す
・同じアクション内でrenderメソッドを複数呼び出すと、エラーになるので、and returnを付ける

リファレンスより抜粋。
部分テンプレ用だと勝手に思ってたけど、ビューを選ぶメソッドなのね。
複数呼び出すと〜というのは

if true
  render :new
else
  render :index
end

こういうのがエラーになるので

if true
  render :new and return
else
  render :index and return
end

こうしろと。

renderにはオプションに加えて省略形がいくつかある

action: 'アクション名'        同じコントローラの別アクションのビューを呼ぶ
                    action: を省略して render :new のように書ける
template: 'コントローラ/アクション'  別コントローラのアクションのビューを呼ぶ
partial: '部分テンプレート名'    部分テンプレートを呼ぶ
layout: 'レイアウト名'        レイアウトを指定する
collection: 渡す変数(@複数形)    部分テンプレートに変数を配列で渡す。
                   部分テンプレート内では単数形の変数名が使える
local: { テンプレ内変数: 渡す変数 } 部分テンプレートに変数を渡す

部分テンプレートに渡す変数は極力collectionで渡す。
eachなどでローカル変数を描画するよりパフォーマンスが向上する。
部分テンプレートを使う場合、partialとcollectionを省略することができ、

<%= render @tweets %>
<%= render partial: 'tweet', collection: @tweets %>

このコードはどちらも同じ意味となる。

form_forの中身(f.○○)

何の記事か?
form_forで挟むf.labelやf.inputなどについてまとめる

基本構文

f.○○ :プロパティ名, オプション

プロパティ名はカラム名のこと。
f.○○では最初のform_forにオブジェクト名(テーブル名)が書かれており省略されている。
よってどのテーブルのどのカラムへ送信するデータなのかを書いてると言える
オプションの書き方はform_forと同じく option: "hoge" や option: :fuga でいい。

代表的なもの

f.label
f.text_field
f.textarea :プロパティ名, size: "縦x横"
f.collection_select :プロパティ名, :オブジェクトの配列, :value属性, :テキスト
f.collection_check_boxed :オブジェクト名, :プロパティ名, :オブジェクトの配列, :value属性, :テキスト 
ストロングパラメータで
params.require(:オブジェクト名).permit({:プロパティ名=>[]}
f.collection_radio_buttons :オブジェクト名, :プロパティ名, :オブジェクトの配列, :value属性, :テキスト

selectはセレクトボックス。配列に並べたいテーブルのレコード、valueに:id、テキストに表示したいカラムを書くことで
レコードを元にしたセレクトボックスを作ることができる。

collection_check_boxedは多(A)対多(B)でのチェックボックス
第1引数はAモデル
第2引数はB_ids
第3引数はBのレコード(配列)
第4引数は検索カラム:id
第5引数は表示したいカラム
と記述することでBのリストを使って入力させ、Aのレコードを作成する。
さらにAとBを繋ぐ中間テーブルにAとBのidを記録する・・という動作ができる。
第2引数でいきなりB_idsというプロパティ(カラム名)が使えるのがまだしっくり来ないが
User_idというカラムがあって、ここがuser_idsという記述では動かないという記事があったため
「user_idとuser_ids」と対応する複数形にすることでRailsがよしなにしてくれてるのだと考えている。
ラジオボタンチェックボックスと挙動は同じ。

form_forまとめ

Railsアプリ開発で必須のform_for(もう既にform_withの時代らしいですが)
色々よしなにしてくれるせいでめっちゃ混乱するおかげで便利なので
やってくれること等をまとめておこうと思います。

form_for
基本構文

<% form_for @model オプション: "" do |f| %>

<% end %>

@modelにはデータを扱うモデルのインスタンスを入れます。
form_for〜endまでの間に

<%= f.label %>
<%= f.input %>

などといった形でform系統のhtmlタグを生成できます。オプションでは主に以下が使えます

url: パス
submit時にデフォルトではcreateを動かしますが、動かすアクションを指定できる

html: { class: "", id: "" }
htmlタグの属性を指定できる

これで済めばまだ良かったのですが、Rails様の気の利かせっぷりが凄いです。

<%= form_for @group do |f| =%>
  省略

これだけの記述で生成されるformタグは

<form class="new_group" id="new_group" action="/groups" accept-charset="UTF-8" method="post">
</form>

こんなことになっています。
特に困惑したのがclass,idを勝手につけること。この自動class, idは
こちらで指定したclass, idがあるとそっちが優先されて自動class, idはつきません。

時間かかってきたので中身のf.シリーズは明日に

はてブ初投稿

このブログはプログラミング(主にHTML&CSSRubyRails、JS&jQuery)勉強中の自分が

・遭遇したエラー
・実装に苦労したもの
・その他ググって残しておきたいと思ったもの

あたりを残しておくためのブログです。
人に読まれることはあんまり想定しない雑な内容になるとは思いますが、
アウトプットの場として積極的に使っていこうと思います。