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.label | labelのlabelタグを表示 |
f.text_field | textのinputタグを表示 |
f.text_area | textareaタグを表示 |
f.date_select | モデルで設定したフィールドをselectタグで選べるようにして表示 |
f.check_box | checkboxのinputタグを表示 |
f.number_field | numberのinputタグを表示 |
f.submit | submitの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した方が楽。
コメント