Rails

form_withがPOSTとPATCHを切り替える条件

form_withがリクエストメソッドをどうやって判断してるのか気になったので調べた。

はじめに結論

モデルのインスタンスの#persisted?メソッドがtrueを返すならPATCH、そうでなければPOST

書かないこと

  • form_withの詳しい説明
  • form_forform_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_withhtml_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?をオーバーライドするの忘れがちな気がするので注意する