Skip to content

Fix Infinite Loop When Accessing Parent in Nested Form Validations#109

Open
adz624 wants to merge 4 commits intotrailblazer:masterfrom
superlanding:fix-infinite-parent-loop-0.3.1
Open

Fix Infinite Loop When Accessing Parent in Nested Form Validations#109
adz624 wants to merge 4 commits intotrailblazer:masterfrom
superlanding:fix-infinite-parent-loop-0.3.1

Conversation

@adz624
Copy link
Contributor

@adz624 adz624 commented Jan 7, 2026

Summary

This PR fixes a critical infinite loop issue that occurs when using the Disposable::Twin::Parent feature with nested form validations. The issue causes
a stack overflow error when attempting to collect validation error messages from forms that have parent-child relationships.

Problem

When using Disposable::Twin::Parent feature in nested forms (e.g., a collection of child forms within a parent form), attempting to retrieve validation
error messages via form.errors.full_messages results in an infinite loop and eventual stack overflow.

Example Scenario

class AlbumForm < Reform::Form
  feature Disposable::Twin::Parent

  property :band
  validates :band, presence: true

  collection :songs do
    property :name

    # Child form needs to access parent data for validation
    validate :unique_name

    def unique_name
      if name == parent.band  # Accessing parent here
        errors.add(:name, "Song name shouldn't be the same as #{parent.band}")
      end
    end
  end
end

When calling form.validate(...) followed by accessing form.errors.full_messages, the application would hang due to infinite recursion.

Root Cause

The issue is in the full_messages_for_nested_fields method in lib/reform/form/active_model/validations.rb:

Before the fix:

def full_messages_for_nested_fields(form_fields)
  form_fields.map { |field| full_messages_for_twin(field[1]) }
end

The method iterates through all form fields to collect error messages from nested forms. However, when Disposable::Twin::Parent is enabled:

  1. Parent forms contain child collections (e.g., songs)
  2. Child forms contain a parent reference back to the parent form
  3. When iterating fields, the algorithm processes:
    • Parent → Children collection → Each child → Parent reference → Children collection → ...
  4. This creates a circular reference: Parent ↔ Child ↔ Parent ↔ Child → ∞
  5. The recursion continues indefinitely until stack overflow

The parent field is not a regular data field—it's a structural reference added by Disposable::Twin::Parent to enable child forms to access parent data
during validation. Including it in error message collection causes the infinite loop.

Solution

Filter out the parent field when collecting nested field errors:

After the fix:

def full_messages_for_nested_fields(form_fields)
  form_fields
    .to_a
    .reject { |field| field[0] == "parent" }  # Skip parent reference
    .map { |field| full_messages_for_twin(field[1]) }
end

Why This Works

  • The parent field is not a data field that can have validation errors
  • It's a reference field used for accessing parent context during validation
  • Error messages from the parent form are already collected when processing the parent itself
  • Skipping the parent field breaks the circular reference chain
  • All legitimate validation errors are still properly collected

Changes

Modified Files

  1. lib/reform/form/active_model/validations.rb (lines 180-184)

    • Added .to_a to ensure form_fields is converted to an array
    • Added .reject { |field| field[0] == "parent" } to filter out parent references
    • Prevents infinite loop by breaking circular reference
  2. test/parent_test.rb (new file)

    • Added comprehensive test case for parent-child validation scenario
    • Tests that validation errors are properly displayed when child forms access parent data
    • Ensures the fix doesn't break existing functionality

adz624 and others added 4 commits January 7, 2026 08:37
Filter out 'parent' field when collecting validation errors from nested
fields to prevent infinite recursion. Also improve test assertion style.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Clean up commented old implementation line.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant