Help us understand the problem. What is going on with this article?

Rails5.1からのform_withでnested_formを扱う方法

More than 1 year has passed since last update.

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を使うと良さそう。
こんな感じのモデルで進めます。

app/models/article.rb
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
app/models/article_detail.rb
class ArticleDetail < ApplicationRecord
  belongs_to :article
end
app/models/article_image.rb
class ArticleImage < ApplicationRecord
  belongs_to :article
end

has_oneのケース

Controllerはこんな感じで定義しといて

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController

  def new
    @article = Article.new
    @article.build_article_detail
  end

  def create
  end
end

fields_forを使うパターン

app/views/articles/new.html.haml
= 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を使うパターン

app/views/articles/new.html.haml
= 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のケース

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController

  def new
    @article = Article.new
    3.times { @article.article_images.build }
  end

  def create
  end
end
app/views/articles/new.html.haml
= 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'} 記事を投稿する
app/views/articles/new.html.haml
= 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タグの挙動を追えてなかったので良い復習ができました。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away