※追記: fields_forにかんして、新しくこちらの記事にまとめたので是非一緒に参照してください。
form_tagとform_forの使い分け
Railsにおけるformを理解するために、最初に理解すべきなのがこの違いです。
Railsのform_for/form_tagの分け方の意図としては、
form_for
: 任意のmodelに基づいたformを作るときに使う
form_tag
: modelに基づかないformを作るときに使う
ということです。
つまり、あるuserモデルに基づいたuserを作成するときはform_for
を使い、
そうではなく、検索窓のような何のモデルにも基づかないformを作りたいときはform_tag
を使うのが原則です。
具体的に何が違って、その用途はどう分かれているのか。
結論としてform_forはモデルオブジェクト扱う上で便利
post先のurlは不要
form_for(@user, url: users_path) #これでuser#createへのpostで
form_for(@user, url: user_path(@user), html: {method: "patch"}) #これがuser#updateへ
と、urlを明示する必要はなく、以下のようにオブジェクトを渡すだけで、urlを推測してくれます。
form_for(@user) #これで@userが新しいときはuser#createに
#@userが既存のときは、user#updateに, 勝手に振り分けます。
とても楽ですね。@userがすでに存在しているかどうかで判断しているようです。(基本動作のcreate/update以外を指定したいときは、url: ,method:
両方指定してあげる必要はあります。)
controllerでもモデルオブジェクトの初期化を楽にできます。
User.new(user)とするだけで、初期化ができます。
form_forヘルパーは、勝手にネストしたパラメーターをつくってくれるのでcontrollerで扱うのも助けてくれます。
<%= form_for @user do |f| %>
<%= f.text_field :name %>
<%= f.text_area :introduction %>
<% end %>
のform_forから受け取るcontrollerで、params[:user]以下に全てのuserのattributesが入るので、あとはparamsのuserをcreateの引数に渡すだけです。
def create(user)
User.create(user) #(エラー処理は省略してます..)
end
とてもシンプルです。
f.text_field :attr_name
で渡される値は、
params[:user][:attr_name] #model名以下にattr入る。
こう入ってくれるからですね。
対して、form_tagは低次元なAPI
特にform_forみたいに暗示的な推測をあまりしないイメージです。htmlの
タグにコンバートしてくれる基本的なヘルパーという認識で。modelに基づかないときに使うと良いのですが、具体的にどういうときかというと、例えば、検索窓とかつくるなら、以下の様にかけます。
<%= form_tag("/search", method: "get") do %>
<%= label_tag(:q, "Search for:") %>
<%= text_field_tag(:q) %>
<%= submit_tag("Search") %>
<% end %>
paramsはネストされず、そのままparams[:attr]でアクセスされたいときに使われることが多い。ちなみに、form_tagからネストしたparamsをつくるには、こうする。
<%= text_field_tag "user[name]", :id => "user_name" %>
#もしくは
<%= text_field :user, :name %>
みたいな書き方をしてやると可能。
ここで注意したいのはform_tag内では、
text_field_tag
が使われて、form_for内ではtext_field
が使われています。
何故か。
text_field_tagとtext_fieldの違い
text_fieldたちだけでなく、そのほか、check_box, check_box_tagなど、他にも_tagがある/無しのパターンがある。
基本的には、
- text_fieldたちのグループ
ActionView::Helpers::FormHelper
モデルを対象とするヘルパー
→ 一次元目のキーをモデル名とする二次元のhashとしてparamsに入り、
そのmodel objectのattributesのみをとる。 - text_field_tagたちのグループ
ActionView::Helpers::FormTagHelper
モデルには関連付けられず、値のみを対象とするヘルパー
→ paramsに一次元のhashとして入る。
使い方
上で再三見せてきてはいるが、使い方。
#text_fieldの使い方
<%= form_for(@user) do |f| %>
<%= f.text_field :name %> #←nameはuserのattributesとして存在しなければいけない。
# ちなみに
# <%= text_field :parent, :child %>ともかける。
# ↑対象としてるモデル以外でネスト作りたいときにもこう使える。
<% end %>
#-> params[:user][:name]に値入る
#text_fieldの使い方
<%= form_tag url, options do %>
<%= text_field_tag :name %>
<% end %>
#-> params[:user]に値入る
ポイント
上でも書いたように、_tagでネストしたparamsをつくることも可能ではある。ここで大事なのは、text_field系はmodel objectに基づいてネストしたparamsをつくるときに扱いやすくて、text_field_tag系は(もっと低次元の動作しかしないので制約も少なく)、一次のhashをつくるときに便利なんだと押さえておくこと。
form_forを使うBest Practiceたち
modelに関係無い引数をform_forに付け足したい?
基本的には、modelに紐づくけど、部分的にmodelに関係ないparamsを送りたい場合はどうすればいいのっていう。modelに関係ない引数は、params[:model名][:ここ]には入れたくないので、そういうときは、form_for内に、f.
にくっつかない形で入れてやればいいというだけ。
#text_fieldの使い方
<%= form_for(@user) do |f| %>
<%= f.text_field :name %>
<%= check_box :agree_contract %> #->params[:agree_contact]が値持つ
<%= check_box :parent, :child %> #->params[:parent][:child]が値持つ
<% end %>
みたいな形で値をparamsにもたせてcontrollerでの処理に使える。
namespaceを扱う
form_forは勝手にcreateだとかupdateのpathを振り分けてくれちゃうわけですが。それは@userの存在確認のみして振り分けているので、デフォだと、てっぺんのcontrollerへのpathを持ってしまいます。
つまりapp/controllers/users_controller
のようにapp/controllers/
以下に直接controller書いてるケースはform_for(@user)
でおk。
一方、app/controllers/admins/users_controller
のようにnamespaceさせている場合は、form_forの自動path振り分け機能に、app/controllers/admins/
以下だよ!と教えてあげるために、form_for([:admins, @user])
で宣言してあげる必要があります。(@userのモデル名からcontrollerの名前を察するところは、railsのルールありきですね〜。)
dbに存在するrecordじゃないんだけど...form_forの恩恵を授かりたい。
見てわかる通り、hashしたparamsを作成する点で、form_forは圧倒的にコードの繰り返し少なく楽に書くことができる。model objectのインスタンスを渡してform_forは使うので、recordじゃなければ無理かと思いきや、方法ありました。dbになくとも、modelクラスを作成して、ActiveModelをincludeすればいけます。(ActiveModelはActiveRecordからdbにアクセスする機能を引いたmoduleと理解してくだせ。)
class User
include ActiveModel::Model
attr_accessor :name
validates :name, presence: true
#この辺にいつも通りmethodもかける。
end
のように、userがdbに保存される系のオブジェクトじゃないときも、ActiveModel::Modelをincludeして、controllerから@user=user.new
でインスタンス渡してやれば、いつも通り、form_forを使うことができます。その際、form_forでuserに渡すattributesに関しては、attr_accessorで宣言しておきましょう。上で紹介したように、text_filed軍団は、model objectのattributesでないものをparams[:model名][:ここ]に入れてくれないので。attributesでないといけないというわけではなくて、@object.attr_nameの形でアクセスできればいいので、理屈的にはmethodでもなんでもobjectからこの形でアクセスされるものが定義されていて、@object.attr_nameがエラーを返さなければそれで良さげである。
インスタンスの子オブジェクト(もしくは他のオブジェクト)も同じformに組み込みたい。
公式ドキュメントの例からいただいて...
<%= form_for @user do |f| %>
<%= f.text_field :name %>
<%= fields_for @user.contact_detail do |cf| %>
<%= cf.text_field :phone_number %>
<% end %>
<% end %>
#-> params[:user][:ここ]にuserのattributes情報
# params[:contact_detail][:ここ]にcontact_detailのattributes情報 が入る。
同様に、関係ないオブジェクトに関しても似たような方法で(field_forに渡すインスタンスをそのオブジェクトのインスタンスにするだけで)できそうです。
以上です。
参考
railsドキュメント
text_filedとtext_filed_tag
[Rails3] form_for と form_tag、text_field と text_field_tag の違い