form(rails)

Rails5.1からはform_tagとform_forはform_withメソッドに統合されています。徐々に使われなくなると思いますが、railsを勉強すると避けては通れないので備忘録にform_tag, forについても記載しています。

form_with

renderで画像遷移しない

form_withは標準でremote: trueになっていて非同期通信をしている。
このため、createアクション等でrenderを返す場合に、画面遷移しない。
local: trueを指定すると、同期通信になるためrenderされるようになる。

= form_with(model: @user, url: root_path, local: true) do |f|
  = f.submit

form_forとform_tag

form_for と form_tag どちらを使うかは、基本的にモデルの有無で判断する。
入力フォームで入力するデータのモデルがあれば form_for を使い、
入力するデータが特にモデルを持っていなければ form_tag を使う。

そもそも引数が異なる。
form_tagにはインスタンスは必要ないが、form_for インスタンスを渡す必要がある。

form_tag

<%= form_tag('/hogehoge', method: :post, class: "clearfix") do %>
  <%= image_tag 'hogehoge.png'%>
  <input placeholder="Nickname" type="text" name="name">
  <textarea cols="30" rows="4" name="text" ></textarea>
  <input type="submit" class="submit-button">
<% end %>

1行目の引数の1つ目はリクエストのパスを。
2つ目はhttpメソッドをpostにするという意味。
httpメソッドは情報をサーバーから得る時はget、送信するときにはpostを使う。

フォームに入力した内容はparamsに格納されて次のコントローラーに渡される。
paramsの中身はname=””で指定した値をキーに持つハッシュとなっている。

params[:name] ==> ”hoge”
という風に情報を取り出せます。

form_for

ヘルパーメソッド。特定のモデルを編集・追加するためのフォームを生成する。
特定のテーブルにレコードだけを新規作成・更新するときに使う。
テーブルに保存するデータを扱う関係でモデルインスタンスを必要とする。

<%= form_for(モデルクラスのインスタンス) do |f| %>
…
<% end %>

form_forは引数のインスタンスが何も情報を持っていなければ自動的にcreateアクションへ、
すでに情報を持っている場合はupdateアクションへ自動的に振り分ける。

<%= f.text_field :name %>
これは下のように変換される

<input id="モデル名_name" name="モデル名[name]" type="text" size="モデルで設定したsize">
f.labellabelのlabelタグを表示 
f.text_fieldtextのinputタグを表示
f.text_areatextareaタグを表示
f.date_selectモデルで設定したフィールドをselectタグで選べるようにして表示
f.check_boxcheckboxのinputタグを表示
f.number_fieldnumberのinputタグを表示
f.submitsubmitのinputタグを表示

form_forへの属性付与

クラス等の属性を付与する場合、インスタンスに続けて html: {class: ‘col s12’} のように入れる。

<%= form_for(resource, as: resource_name, url: session_path(resource_name), html: {class: 'col s12'}) do |f| %>
html: {class: 'col s12', id: 'hoge'}

個々の要素に属性をつける場合はこう。

<%= f.submit "Log in", class: "hogehoge" %>

余談だがsubmit等のボタンはinput要素なのでインライン要素。
つまり中央寄せする場合は親要素にtext-align: center;を設定する。

フォームに入力されたデータの処理

重要な点が2つ。

  • 入力されたデータはparamsに格納されてcontrollerへ渡る。
  • submitした際のリクエスト先はインスタンスによって自動的に決まる。

1つめのデータに関してですが、これはparamsに格納されます。form_tagでもform_forでもparamsに格納されます。(形式は微妙に違いますが)
このparamsを加工することによって情報を取り出していきます。
取り出した内容を.saveもしくは.createすることでテーブルに保存します。

2つ目のリクエスト先ですが、form_forを使う場合は自動的に決定されます。
これに関しては深く考える必要がないですが、唯一ルーティングでネストした場合が少しだけ複雑です。

paramsに格納されたデータについて

paramsにはフォームに入力された情報以外にもたくさんの情報が格納されています。
以下はform_tagを使った例ですが、formの要素はimageとtextだけですが、他にも色々入っています。

{
  "utf8"=>"✓",
  "authenticity_token"=>"/3QchQig5NA6k=",
  "image"=>"http://sample/hoge.jpg",
  "text"=>"hello!",
  "controller"=>"tweets",
  "action"=>"create"
}

ここから分かるのはparamsはハッシュ形式でデータを持つということです。

form_tagとform_forで返されるparamsの違い

form_tag ただのハッシュです。

{
  "utf8"=>"✓",
  "authenticity_token"=>"/3QchQig5NA6k=",
  "image"=>"http://sample/hoge.jpg",
  "text"=>"hello!",
  "controller"=>"tweets",
  "action"=>"create"
}

form_for ハッシュのネストになっています。

{
  "utf8"=>"✓",
  "authenticity_token"=>"/3QchQigdEI5NA6k=",
  "review"=> {
         "rate"=>"5", 
         "review"=>"It's reasonable"
        },
  "action"=>"create",
  "controller"=>"reviews",
  "product_id"=>"4"
}

requireメソッド

form_forに入力されたデータはparamsの中にネストされたハッシュの形で入っています。

<%= form_for(@book) do |f| %>
  <%= f.text_field :name %>
  <%= f.text_area :detail %>
<% end %>
params
# { book: { name: "入力された名前", detail: "入力された詳細" } }

外側のハッシュのkeyはform_forの引数のインスタンスのモデル名(この場合book)となり、valueにフォームに入力された内容がハッシュで入っています。

フォームの内容を取り出そうとすると、一旦外側のハッシュにkeyを指定して、その後に内側のハッシュにkeyを指定する必要があります。

nameを取り出す場合
params[:book][:name]

この1段階目の外側のハッシュにキーを渡す処理をrailsでは通常requireメソッドを使って行います。

params.require(:book)

上記のように内側のハッシュだけを取り出します。
ちなみに何故requireメソッドを用いるのかは知りません。
可読性以外の要素はないと思うのですが、慣例でそうなっています。
params[:book]でも問題なく動きます。

submit後のリダイレクト先

引数のインスタンスによって自動的に決定されます。

<%= form_for(@book) do |f| %>

@bookを.newで渡した場合にはcreateアクションへ
find等で既存の@bookを渡した場合はupdateアクションへ
リダイレクトされます。HTTPリクエストもいいようにしてくれます。

具体的にはresourcesでルーティングした場合には以下へリダイレクトされます。
createの場合は books_path (/books)
updateの場合は book_path (/books/:id)

resourcesでネストした場合

例えば以下のようにネストした場合を考えます。

resources :genres do
  resources :books
end

この時のbooksのcreateとupdateのパスは以下になります。
create: genre_books_path (/genres/:genre_id/books)
update: genre_book_path (/genres/:genre_id/books/:id)

先ほどとパスが変わっているのでそのままだとformのリダイレクト先でエラーがでます。
この場合には以下のように2つのインスタンスを引き渡すことでパスを補完してくれます。

<%= form_for(@genre, @book) do |f| %>

親モデルのインスタンス(もしくは親モデルのid)を第1引数、子モデルのインスタンスを第2引数に設定します。

入力内容を保存

createもしくはupdateアクションにリダイレクトされるため、controllerにアクションを作成します。

.createメソッドを使って保存

  def create
    Hogehoge.create(hogehoge_params)
  end

  private
  def hogehoge_params
    params.permit(:text)
  end

.createメソッドの引数は保存するテーブルのカラム名: valueの繰り返し。
paramsは情報をハッシュとして持っているので、params.permitやparams.requireで抜粋したデータはそのまま.createメソッドの引数として使えます。

.createメソッドは.newと.saveを同時に行ってくれる。
以下のように引数を渡して保存するのと同義。

hogehoge = Hogehoge.new(name: "takashi", text: "Nice to meet you!")
hogehoge.save

入力フォーマット

date_select

= f.date_select :birthday, start_year: 1900, end_year: Time.current.year, use_month_numbers: true, default: { year: 1990, month: 1, day: 1 }

date_selectフィールドにした場合、paramsに名前(1i), 名前(2i), 名前(3i)という形で格納される。

Parameters: {  "birthday(1i)"=>"1990", "birthday(2i)"=>"1", "birthday(3i)"=>"1", "commit"=>"次へ進む"}

一旦、date型に変換してparamsにマージする。

birthday = Date.new(params[:"birthday(1i)"].to_i, params[:"birthday(2i)"].to_i, params[:"birthday(3i)"].to_i)
params.permit(:nickname).merge(birthday: birthday)

collection_select

collection_check_boxesと同じような引数を設定する。
違いは第5引数にprompt、第6引数にclassが指定できる。

= f.collection_select( :category_id, @categories, :id, :name,{prompt: "全て"}, {class: "category-root"})
= f.select :category do
  %option すべて
  - @categories.each do |c|
    %option.category-root= c.id
強引にこういうやり方もできるけど、素直にcollectionした方が楽。

コメント