dynamic-nested-attributes

Implement Rails nested attributes with dynamic add/remove functionality using Turbo Streams and Simple Form. Use when building forms where users need to manage multiple child records (has_many associations), add/remove nested items without page refresh, or create bulk records inline.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "dynamic-nested-attributes" with this command: npx skills add rolemodel/rolemodel-skills/rolemodel-rolemodel-skills-dynamic-nested-attributes

Dynamic Nested Attributes

Overview

Implement Rails nested attributes with dynamic add/remove functionality using Turbo Streams and Simple Form. This pattern allows users to add and remove associated records inline within a parent form.

When to Use

  • Building forms where users need to manage multiple child records (has_many associations)
  • Adding/removing nested items without page refresh
  • Bulk creation or editing of associated records
  • Forms requiring progressive disclosure of additional fields

Key Components

1. Form Object or Model

  • Accepts nested attributes for the association
  • Use accepts_nested_attributes_for :association_name in the model or form object

2. Main Form View

Create a form that includes:

  • simple_fields_for for rendering existing nested items
  • A container element with an ID for appending new items (e.g., #accessories)
  • A link to add new items that triggers a Turbo Stream request

Example:

= simple_form_for resource do |f|
  = f.simple_fields_for :items do |ff|
    = render 'item_fields', f: ff, resource:

= render 'add_button', index: resource.items.size

Add Button Partial (_add_button.html.slim):

-# locals: (index:)
= link_to icon('add'), new_parent_item_path(index: index),
  id: 'add_button', class: 'btn', data: { turbo_stream: true }

3. Nested Fields Partial

Create a partial (e.g., _item_fields.html.slim) that:

  • Wraps fields in a unique container with an ID based on index
  • Includes a data controller for remove functionality
  • Shows a delete button for all records
  • Includes all form inputs for the nested item

Example:

fieldset id="item_#{f.index}" controller='destroy-nested-attributes'
  = f.hidden_field :_destroy, data: { destroy_nested_attributes_target: 'input' }
  = f.input :name
  = f.input :quantity
  .form-row__actions
    = button_tag icon('delete'), type: 'button', class: 'btn btn-delete',
      data: { action: 'destroy-nested-attributes#perform' }

4. Controller Actions

Implement a new action that:

  • Builds a new nested item
  • Accepts index parameter for tracking position

Example:

def new
  @item = Item.new
end

5. Turbo Stream Response

Create a new.turbo_stream.slim view that:

  • Updates the "add" button with incremented index
  • Appends the new nested fields to the container
  • Uses index parameter to ensure unique field names
  • Works with non-persisted parents by using a symbol and empty URL

Example:

= turbo_stream.replace 'add_button', partial: 'add_button', locals: { index: params[:index].to_i + 1 }

= simple_form_for :parent, url: '' do |f|
  = f.simple_fields_for :items_attributes, @item, index: params[:index] do |ff|
    = turbo_stream.append 'items', partial: 'item_fields', locals: { f: ff }

6. Remove Stimulus Controller

Create a Stimulus controller to handle client-side removal:

Example JavaScript:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ['input']
  static classes = ['destroyed']

  connect() {
    if (!this.hasDestroyedClass) {
      this.element.setAttribute(`data-${this.identifier}-destroyed-class`, 'is-hidden')
    }
  }

  perform() {
    this.inputTarget.value = '1'
    this.element.classList.add(this.destroyedClass)
  }
}

Implementation Checklist

  • Add accepts_nested_attributes_for to model/form object
  • Create main form with simple_fields_for and container element
  • Create nested fields partial with remove functionality
  • Implement controller new action with index support
  • Create turbo_stream response view
  • Add Stimulus controller for client-side removal
  • Update routes to support nested resource creation
  • Update strong parameters to permit nested attributes
  • Add policy authorization if using Pundit

Common Patterns

Dynamic Collections Based on Parent Selection

Pass filtered collections to nested partials:

= render 'item_fields', f: ff, resource:, collection: resource.items

Routes Example

If there is not an existing new route in use, use the following pattern

resources :items, only: [:new]

If one does exist, create a new namespaced controller

namespace :parent do
  resources :items, only: [:new]
end

Strong Parameters Example

def item_params
  params.require(:parent).permit(
    :category,
    :subcategory,
    items_attributes: %i[
      name
      quantity
      part_id
      optional
      hidden
    ]
  )
end

Related Patterns

  • Turbo Frame inline editing
  • Stimulus data controller integration
  • Form object pattern for bulk operations
  • Policy-scoped collections for associations

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

bem-structure

No summary provided by upstream source.

Repository SourceNeeds Review
General

optics-context

No summary provided by upstream source.

Repository SourceNeeds Review
General

routing-patterns

No summary provided by upstream source.

Repository SourceNeeds Review