[Rails]accepts_nested_attributes_forの使い方

#目的
関連を持ったモデルをまとめて操作するために使われるaccepts_nested_attributes_forの使い方を整理する
・導入
・サンプルコード
・テスト
ついでにcocoonについても簡単に使えるようにする

#環境
Ruby 2.2.2 p95
Rails 4.2.6
RSpec 3.4.4
FactoryGirl 4.7.0

なおこのソースコードは[Github](https://github.com/seimiyajun/training_accepts_nested_attributes_for/)に置いてあります。

#事前準備
##環境
Railsアプリを作って、

```console:console
$ rails new hoge
```

Gemfile更新して、
※cocoonはフィールドを動的に生成するところで使っています。
※rspecやfactory_girlなどはテストに必要なgemなので、テストを書かないとか、違うツールを使う場合は省略して構わないです。

```rb:Gemfile
gem 'cocoon'

group :development, :test do
  gem 'rspec-rails'
  gem 'factory_girl_rails'
end

group :test do
  gem 'capybara'
end
```

`bundle install`しつつ、RSpecをインストール。

```console:console
$ bundle install
$ rails generate rspec:install
```

cocoon用の設定をapplication.jsに追加

```assets/javascripts/application.js
//= require cocoon
```

##モデル
とりあえずモデルを2つつくります。
親は全部欲しいのでscaffoldで、子はモデルのみ欲しいので個別につくります。

```console:console
$ rails generate scaffold parent name:string
$ rails generate model child name:string parent:references
```

親には`has_many :children`を追加します。
親が削除された時に合わせて子も削除されるよう`dependent: :destroy`もつけます。
`accepts_nested_attributes_for`を使うと、親のformに子のフィールドを追加するだけで一気にcreate, updateできるようになります。
削除フラグで削除できるよう`allow_destroy: true`をつけます。

```parent.rb
class Parent < ActiveRecord::Base
  has_many :children, dependent: :destroy
  accepts_nested_attributes_for :children, allow_destroy: true
end
```

子(models/child.rb)はそのままで良いです。

migrationします。

```console:console
$ rake db:migrate
```

#コード
このままだと親しかつくれないので、親と一緒に子を作れるようにします。
##View
こんな感じでフィールドを追加します。
`association_insertion_method`はcocoonのメソッドで行を動的に追加するものです。
チェックボックスは削除のためのものです。（cocoonのlink_to_remove_associationを使っても良いかと思います）

```views/parents/_form.html.erb
<%= form_for(@parent) do |f| %>
<!-- 中略 -->
  <div class="field">
    <%= f.fields_for :children do |c| %>
      <%= render 'child_fields', f: c %>
    <% end %>
  </div>
  <div class="field">
  <%= link_to_add_association '子を追加', f, :children,
    data: { association_insertion_method: 'before' } %>
  </div>
<!-- 中略-->
<% end %>
```

子のフィールドを個別に用意します。
`_モデル名_fields`という名前をつけるとcocoonが自動的に読み込んでくれます。

```views/parents/_child_fields.html.erb
  <div class="field">
    <%= f.label 'Child name' %><br>
    <%= f.text_field :name %>
    <% if @parent.persisted? %>
      <%= f.label '削除' %>
      <%= f.check_box :_destroy %>
    <% end %>
    <br>
  </div>
```

##Controller

###strong parameters
Childモデルのattributeをstrong parametersに追加しておきます。
削除フラグを利用するため、`_destroy`も追加します。
_destroyがtrueだと削除が実行されます。

```controllers/parents_controller.rb
  private
    def parent_params
      params.require(:parent).permit( :name, children_attributes:[ :id, :name, :parent_id, :_destroy ] )
    end
```

以上。
これで一括生成できます。

#テスト
テストの仕方（自分が書きたかったのはここだったりする）

##FactoryGirl
親子それぞれのfactoryと、子も含んだ親factoryを用意します。

```spec/factories/parents.rb
FactoryGirl.define do
  factory :parent do
    name "MyString"
  end
  
  factory :parent_with_child, class: Parent do
    name "MyString"
    
    after( :create ) do |parent|
      create :child, parent: parent
    end
  end  
end
```

```spec/factories/children.rb
FactoryGirl.define do
  factory :child do
    name "MyString"
    parent nil
  end
end
```

##RSpec
###controller
`attributes_for`でattributeのみ取り出して、mergeしてpost/patchします。

```spec/controllers/parents_controller_spec.rb
RSpec.describe ParentsController, type: :controller do
  describe "POST #create" do
    context "with valid params" do
      it "creates a new Parent" do
        params = { children_attributes: [ FactoryGirl.attributes_for( :child ) ] }
        expect {
          post :create,
          parent: FactoryGirl.attributes_for( :parent ).merge( params )
          }.to change( Parent, :count ).by( 1 ).and change( Child, :count ).by( 1 )
      end
    end
  end
```

###feature
まずはfeatureテストを作って、

```console:console
$ rails g rspec:feature patch
```

parent_with_childを使い、一括でcreateできます。

```spec/features/patches_spec.rb
RSpec.feature "Patches", type: :feature do
  scenario 'patch test' do
    parent = FactoryGirl.create( :parent_with_child )
    expect( Parent.count ).to eq 1
    expect( Child.count ).to eq 1
  end
end
```

#参考
関連について
[[Rails] 親ー子ー孫と関係のあるモデルを一括で削除する](http://qiita.com/seimiyajun/items/ffefdfc74b9fce76a538)

フォームの動的生成について
[nested_form はもう古い！？ Cocoon で作る1対多のフォーム](http://qiita.com/Matsushin/items/4829e12da2834d6e386e)
[Qiitaのnested_formの検索結果](http://qiita.com/search?q=nested_form)

FactoryGirlについて
[(ﾟ∀ﾟ)o彡 sasata299's blog - factory_girl で最低限知っておきたい4つの使い方](http://blog.livedoor.jp/sasata299/archives/51931175.html)