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

## 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](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が提示しているサンプルコード

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