51
70

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.

[Rails]accepts_nested_attributes_forの使い方

Last updated at Posted at 2016-06-09

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

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

なおこのソースコードはGithubに置いてあります。

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

console
$ rails new hoge

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

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
$ bundle install
$ rails generate rspec:install

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

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

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

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
$ 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
$ 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] 親ー子ー孫と関係のあるモデルを一括で削除する

フォームの動的生成について
nested_form はもう古い!? Cocoon で作る1対多のフォーム
Qiitaのnested_formの検索結果

FactoryGirlについて
(゚∀゚)o彡 sasata299's blog - factory_girl で最低限知っておきたい4つの使い方

51
70
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
51
70

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?