Edited at

【Rails】form_for/form_tagの違い・使い分けをまとめた

More than 1 year has passed since last update.

※追記: 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をつくるときに扱いやすくて、textfield_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に組み込みたい。

公式ドキュメントの例からいただいて...


html.erb

<%= 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 の違い