Railsのaccepts_nested_attributes_forについて解説してみた。

目次

はじめに

accepts_nested_attributes_forメソッドがあまりに初心者殺しというか、製作者のDHHさんにこのメソッドを
抹殺したい・・・
と言わせるくらいの極悪メソッドであるのですが、現在はこのメソッドを使わざるを得ない状況なので初学者の方を救うべく記事をまとめます。

accepts_nested_attributes_forメソッドとは

accepts_nested_attributes_forメソッドは
モデル同士が関連付けられている時に、ネストさせることで一度にまとめてレコードの更新ができるようにするメソッドです。

公式

Active Record Nested Attributes
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

前提

公式に則ってモデルやコントローラ、ビューを定義しておきます!

bookモデル

Column Type Option
name string null: false, unique: true
author references null: false, foreign_key: true
page references null: false, foreign_key: true

Association

has_one: author
has_many: pages
accepts_nested_attributes_for :author, :pages

authorモデル

Column Type Option
name string null: false, unique: true

Association

belongs_to: book

pageモデル

Column Type Option
name string null: false, unique: true

Association

belongs_to: book

コントローラー

# in app/controllers/books_controller.rb
class BooksController < ApplicationController

  # ...省略
  def create
    # ...省略
  end

  def update
    @book = Book.find(params[:id])
    if @book.update(book_params)
      redirect_to book_path(@book), notice: '更新しました'
    else
      render :edit
    end
  end

  def book_params
    params.require(:book).permit(:name, book_attributes: [:id, :name, :_destroy])
  end
end

viewファイル

# in app/views/books/new.html.erb
<%= form_with @book do |f| %>
    <%= f.text_field :name %>
    <%= f.field_for :関連付けているモデル名 |g| %>
      <%= g.text_field :関連付けているモデルのカラム名 %>
      削除する <%= g.check_box :_destroy %>
    <% end %>
  <% f.submit %>
<% end %>

1対1のアソシエーションの場合

抽象的な書き方

# in app/models/モデル名.rb
class モデル名 < ActiveRecord::Base
  has_one :関連づけているモデル名
  accepts_nested_attributes_for :関連づけているモデル名
end

具体的な書き方

# in app/models/book.rb
class Book < ActiveRecord::Base
  has_one :author
  accepts_nested_attributes_for :author
end

ターミナル

params = { book: { name: 'Harry Potter', author_attributes: { name: 'J・k・Rowling' } } }
book = Book.create(params[:book])
book.author.id # => 2
book.author.name # => 'J・k・Rowling'
params = { book: { author_attributes: { id: '2', name: 'Joanne Rowling' } } }
book.update params[:book]
book.author.name # => 'Joanne・Rowling'

オプションについて

update_onlyオプション

新たに作成することなく、データの更新だけしたい場合につけます。

class book < ActiveRecord::Base
  has_one :author
  accepts_nested_attributes_for :author, update_only: true
end

ターミナル

params = { book: { author_attributes: { name: 'Joanne Rowling' } } }
book.update params[:book]
book.author.id # => 2
book.author.name # => 'Joanne・Rowling'

allow_destroyオプション

親要素が削除された時、関連付けている情報もまとめて削除するためのオプションです。
基本的にはあったほうが良いオプションですね。
例)何かのサイトで退会したのにずっと関連情報が保持されてしまい、データベースを圧迫してしまう。

class book < ActiveRecord::Base
  has_one :author
  accepts_nested_attributes_for :author, allow_destroy: true
end

ターミナル

book.author_attributes = { id: '2', _destroy: '1' }
book.author.marked_for_destruction? # => true
book.save
book.reload.author # => nil

1対多のアソシエーションの場合

抽象的な書き方

# in app/models/モデル名.rb
class モデル名 < ActiveRecord::Base
  has_many :関連づけているモデル名
  accepts_nested_attributes_for :関連づけているモデル名
end

具体的な書き方

渡されたデータ(ハッシュ)に_destroy属性がtrueで入っていなければ、新しいidが付与されて関連データが作成されます。
下の例だと、_destroy: ‘1’がついているものは弾かれます。

# in app/models/book.rb
class Book < ActiveRecord::Base
  has_many :pages
  accepts_nested_attributes_for :pages
end

ターミナル

params = { book: {
  name: 'Harry Potter', pages_attributes: [
    { name: 'first' },
    { name: 'second' },
    { name: '', _destroy: '1' } # this will be ignored
  ]
}}

book = Book.create(params[:book])
book.pages.length # => 2
book.pages.first.name # => 'first'
book.pages.second.name # => 'second'

allow_destroyオプション

1対1のアソシエーションの時と同じ流れです!!

class Book < ActiveRecord::Base
  has_many :pages
  accepts_nested_attributes_for :pages, allow_destroy: true
end

params = { book: {
  pages_attributes: [{ id: '2', _destroy: '1' }]
}}

book.attributes = params[:book]
book.pages.detect { |p| p.id == 2 }.marked_for_destruction? # => true
book.pages.length # => 2
book.save
book.reload.pages.length # => 1

reject_ifオプション

弾きたいものがある場合、procを用いて指定することもできます。
下記の例だと、空の文字列が入っているデータを弾いています。

class Book < ActiveRecord::Base
  has_many :pages
  accepts_nested_attributes_for :pages, reject_if: proc { |attributes| attributes['title'].blank? }
end

ターミナル

params = { book: {
  name: 'Harry Potter', pages_attributes: [
    { name: 'first' },
    { name: 'second' },
    { name: '' } # this will be ignored
  ]
}}

book = Book.create(params[:book])
book.pages.length # => 2
book.pages.first.name # => 'first'
book.pages.second.name # => 'second'

終わりに

公式を整理するだけでもかなりのボリュームになってしまいました。
しかし、基本的な書き方をマスターすれば応用が効きますので、オプションなどはしっかりと把握しておきましょう!

https://hirocorpblog.com/roadmap-engineer/

よかったらシェアしてね!
  • URL Copied!
  • URL Copied!
目次
閉じる