Ruby on Railsで、ネストしたい時のform_withの使い方
Rails 5.1.x以降でフォームを扱うときは、form_with
を使うことが推奨されてます。
今まで使っていたform_forやform_tagはRails6系では非推奨になる予定なので、
使ってる人は早めに書き換えておいた方が良いです。
form_with
のドキュメント
https://api.rubyonrails.org/v5.1/classes/ActionView/Helpers/FormHelper.html#method-i-form_with
form_for
,form_tag
が非推奨になる経緯のissue
Provide form_with as a new alternative to form_for/form_tag #25197
https://github.com/rails/rails/issues/25197
form_withの使い方
DHHが提示しているサンプルコード
# Passing model: @post will 1) set scope: :post, 2) set url: url_for(@post)
form_with(model: @post) do |form|
form.text_field :title # Will reference @post.title as normal
form.text_area :description, "Overwrite @post.description if present, if not, it will still work"
form.submit
end
form_with(scope: :post, url: posts_path) do |form|
form.text_field :title # post[title]
form.text_area :description, "Overwrite @post.description or ignore if it's not present"
form.submit
end
# Submits title=X&description=Y
form_with(url: different_path, class: 'something', id: 'specific') do |form|
form.text_field :title, 'This has is the value of the title'
form.text_area :description, class: 'No value has been supplied here'
form.fields(:permission) do |fields|
# on/off instead of positional parameters for setting values
fields.check_box :admin, on: 'yes', off: 'no'
end
form.select :category, Post::CATEGORIES, blank: 'None'
form.select :author_id, Person.all.collect { |p| [ p.name, p.id ] }, blank: 'Pick someone'
form.submit
end
という訳で本題のnested_form
について
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
にある通りモデルの定義はaccepts_nested_attributes_for
を使うと良さそう。
こんな感じのモデルで進めます。
class Article < ApplicationRecord
attr_accessor :article_details_attributes, :article_images_attributes
has_one :article_detail
has_many :article_images
accepts_nested_attributes_for :article_detail, :article_images
end
class ArticleDetail < ApplicationRecord
belongs_to :article
end
class ArticleImage < ApplicationRecord
belongs_to :article
end
has_oneのケース
Controllerはこんな感じで定義しといて
class ArticlesController < ApplicationController
def new
@article = Article.new
@article.build_article_detail
end
def create
end
end
fields_for
を使うパターン
= form_with model: @article, class: 'form' do |article|
.form-group
= article.label(:title, 'タイトル')
= article.text_field :title, {class: 'form-control'}
= article.fields_for :article_detail do |article_detail|
.form-group
= article_detail.label(:memo, 'メモ')
= article_detail.text_field :memo, {class: 'form-control'}
%button{type: 'submit', class: 'btn btn-primary'} 記事を投稿する
こんなパラメータに
=> <ActionController::Parameters {"article"=>{"title"=>"タイトル", "article_detail_attributes"=>{"memo"=>"メモ"}}, "controller"=>"articles", "action"=>"create"} permitted: false>
fields
を使うパターン
= form_with model: @article, class: 'form' do |article|
.form-group
= article.label(:title, 'タイトル')
= article.text_field :title, {class: 'form-control'}
= fields model: :article_detail do |article_detail|
.form-group
= article_detail.label(:memo, 'メモ')
= article_detail.text_field :memo, {class: 'form-control'}
%button{type: 'submit', class: 'btn btn-primary'} 記事を投稿する
こんなパラメータに
=> <ActionController::Parameters {"article"=>{"title"=>"タイトル", "article_detail_attributes"=>{"memo"=>"メモ"}}, "controller"=>"articles", "action"=>"create"} permitted: false>
has_manyのケース
class ArticlesController < ApplicationController
def new
@article = Article.new
3.times { @article.article_images.build }
end
def create
end
end
= form_with model: @article, class: 'form' do |article|
.form-group
.input-field
= article.label(:title, 'タイトル')
= article.text_field :title, {class: 'form-control'}
= article.fields_for :article_images do |article_images|
.form-group
.input-field
= article_images.label(:url, 'メモ')
= article_images.text_field :url, {class: 'form-control'}
%button{type: 'submit', class: 'btn btn-primary center'} 記事を投稿する
= form_with model: @article, class: 'form' do |article|
.form-group
.input-field
= article.label(:title, 'タイトル')
= article.text_field :title, {class: 'form-control'}
= article.fields_for :article_images do |article_images|
.form-group
.input-field
= article_images.label(:url, 'url')
= article_images.text_field :url, {class: 'form-control'}
%button{type: 'submit', class: 'btn btn-primary center'} 記事を投稿する
パラメータはこんな感じ
=> <ActionController::Parameters {"article"=>{"title"=>"タイトル", "article_images_attributes"=>{"0"=>{"url"=>"メモ"}, "1"=>{"url"=>"メモ"}, "2"=>{"url"=>"メモ"}}}, "controller"=>"articles", "action"=>"create"} permitted: false>
まとめ
form_withはモデルにないattributeでも送信できるので、使い勝手良い。
ここ最近、ずっとAPIしか開発してなくてformタグの挙動を追えてなかったので良い復習ができました。