Jonathan Bennett

Mastering Nested Attributes

Here are a few quick tips to help you sidestep some common issues with accepts_nested_attributes:

1. Don’t Forget the _attributes

This catches me all the time—be sure to append _attributes when using fields for a nested object.

Example:

  • In your view: form.fields_for :profile_attributes
  • In your controller:
      params.require(:user).permit(profile_attributes: [:name, :description])
    

2. Prevent Unintended Object Replacement

By default, accepts_nested_attributes will delete an existing object and create a new one if it receives an ID it doesn’t recognize. This behaviour can unintentionally destroy data or break associations.

To ensure existing objects are only updated and not replaced, use update_only: true:

accepts_nested_attributes_for :profile, update_only: true  

3. Deeply Nested Attributes

Nested objects can also have their own accepts_nested_attributes. If your User has an Account, and the Account has a Profile, you can update everything in one go:

<%= form_with model: @user do |form| %>  
  <%= form.fields_for :account_attributes do |account_fields| %>  
	<%= account_fields.fields_for :profile_attributes do |profile_fields| %>  
	  <%= profile_fields.text_field :name %>  
	<% end %>  
  <% end %>  

  <%= form.submit %>  
<% end %>  

And a single controller can process it while still using strong parameters:

class UsersController < ApplicationController  
  def update  
	@user = Current.user  
	@user.update(user_params)  
	# …  
  end  

  def user_params  
	params.require(:user).permit(  
	  :email_address,  
	  account_attributes: [profile_attributes: [:name]]  
	)  
  end  
end  

This approach keeps your code structured and protects against unwanted parameter injection by leveraging strong parameters at every level.