27
43

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ちょんまげサークルAdvent Calendar 2018

Day 2

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

Last updated at Posted at 2018-12-01

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タグの挙動を追えてなかったので良い復習ができました。

27
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
43

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?