form_withがリクエストメソッドをどうやって判断してるのか気になったので調べた。
はじめに結論
モデルのインスタンスの#persisted?メソッドがtrueを返すならPATCH、そうでなければPOST。
書かないこと
-
form_withの詳しい説明 -
form_forとform_withの違い
環境
- Ruby 2.5.0
- Rails 5.1.4
コードを追う
form_withの動き
form_withの定義は actionview/lib/action_view/helpers/form_helper.rbの745行目
この中でhtml_options_for_form_withを呼んでhtml_options(<form>要素の属性値)を組み立ててる。
html_options = html_options_for_form_with(url, model, options)
html_options_for_form_withの定義は同じファイルの1538行目
モデルインスタンスの#persisted?がtrueを返すとき、html_options[:method]に:patchが設定される。
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
#persisted?が未定義だったりtrue以外を返すときはhtml_options[:method]が未設定になる。
その場合はどうなるか。
form_withでhtml_optionsを組み立てたあとの次の行、form_tag_with_bodyを呼ぶと内部で処理がform_tag_html > extra_tags_for_formと継続していく。
extra_tags_for_formで、html_opitions[:method]が未設定だったり空文字だったときは"post"が設定されるようになっている。
モデルの#persisted?の定義
#persisted?はActiveModelで定義されている。
APIドキュメントにあるように、デフォルトでfalseを返すので必要に応じてオーバーライドする。
http://api.rubyonrails.org/classes/ActiveModel/Model.html
https://github.com/rails/rails/blob/v5.1.4/activemodel/lib/active_model/model.rb#L93
ActiveRecordではこれをオーバーライドしてレコードが既存のものかチェックを行っている。
https://github.com/rails/rails/blob/v5.1.4/activerecord/lib/active_record/persistence.rb#L98
まとめ
-
#persisted?によってform_withのリクエストメソッドが自動判別される - ActiveModelをincludeしたクラスを作るとき、
#persisted?をオーバーライドするの忘れがちな気がするので注意する