Repository navigation
Conversation
Before swapping nested_form's blueprint mechanism for inert <template> elements, write specs for the cases that mechanism is the only thing covering. Three were predicted to be broken; one of them was not. Not a bug, now pinned: a deeply nested row added inside a freshly added, unsaved parent row. The inner add link names a page-global blueprint whose parent index is still the literal 'new_nested_field_tests' placeholder, so this looked like it had to produce a phantom second parent. It does not -- the parent prefix is resolved from the DOM at click time, and the saved records come out right. The spec stays as a regression test. Pending, confirmed broken: a modal form and the page behind it both emit a blueprint under the same page-global id, so adding a row inside the modal takes its markup from the form behind it and the input is named for the wrong model; and a row the user added cannot be removed once the form comes back from a failed save and allow_destroy is false, because the destroy button is gated on the blueprint's child_index, which a re-rendered row does not carry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rints A nested association used to be cloned from an HTML string, HTML-escaped into a data-blueprint attribute on a page-global div emitted after the form. It now renders an inert <template> as the last child of its own .tab-content, and the association's add button finds it by name within its control group. Position carries what the global id used to: there is nothing page-global left to collide, a child's template rides along inside its parent's and needs no prefix arithmetic, and the markup is escaped once by ERB rather than escaped into an attribute and unescaped by the browser. RailsAdmin::FormBuilder grows #nested_fields_for, #nested_add_button and #remove_button, and wraps Rails' own #fields_for_nested_model for the per-child .fields node. link_to_add and link_to_remove stay as deprecated aliases, since overriding _form_nested_many or _form_nested_one is a documented customization path; both map onto the new methods argument for argument. The vendored jQuery plugin and window.nestedFormEvents are gone, replaced by src/rails_admin/nested-form.js, which emits rails_admin.nested_field_added and rails_admin.nested_field_removed in the naming style ui.js and remote-form.js already use. Two things the mechanism was the only thing covering are fixed: a nested form inside a modal no longer takes its rows from the form behind it, and a row the user added stays removable when the form comes back from a failed save with allow_destroy false. Both had pending specs from the previous commit. The placeholder index is tagged by nesting depth (new_1_nested_field_tests) and never by association name: a name-tagged placeholder is a prefix of the ones in the templates nested inside it, and substituting the outer one would eat them. Both controls are now <button type='button'>, so no Content Security Policy has to admit a javascript: URL; the remove control needs its own UA chrome turned off in theming.scss. The gemspec dependency and the dead remove_nested_one_fields handling go separately. Mongoid is not verified yet - nested_template_for builds its sample child with abstract_model.new, and embedded documents are the case most likely to object. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing has required nested_form since the previous commit, so the gemspec dependency goes. The gem has been archived since 2021, which is what #3723 asked about. The lockfiles are not tracked - .gitignore excludes gemfiles/*.lock and .git/info/exclude excludes gems.locked - so regenerating them is a local step; bundle list no longer names the gem. The remove_nested_one_fields class goes with it. Nothing has generated it since the has_one remove button was reworked, and git log -S shows the click handler in ui.js and the styles in theming.scss surviving three rewrites while waiting for markup that never came back. Refs #3723 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A template renders one sample child, so an association that can reach itself - Mongoid's recursively_embeds_many, or a self-referential has_many with accepts_nested_attributes_for - has no terminating case. On master this hung the request, climbing past 560 MB resident in seven minutes and still growing; since the templates landed it raises SystemStackError instead. Neither is a form. One association may now repeat at most MAX_NESTED_FORM_RECURSION times down a chain of nested forms. Any infinite chain has to revisit some association, there being finitely many of them, so capping the repeats of one association bounds every cycle - including one that closes through several distinct associations. Counting repeats rather than depth is what keeps FieldTest -> nested_field_tests -> deeply_nested_field_tests unlimited however deep it runs, which a flat depth cap would not have done. The count comes off @object_name, which already spells the chain, so nothing has to be threaded through generate and a partial someone has overridden keeps working. Where no template is rendered the add button is not rendered either, so the UI does not offer something that quietly does nothing; nested_add_allowed? is the one place that decides, and inline_add folds into it. recursively_embeds_* had no integration coverage at all - it appeared only inside unit specs defining throwaway classes - so the dummy app grows RecursiveFieldTest on Mongoid. ActiveRecord gets the same shape from the existing Category, whose table already carries parent_category_id, so no migration. Refs #3723 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three audiences, because the configuration API did not change but everything around it did: applications that override _form_nested_many or _form_nested_one, applications that hooked window.nestedFormEvents or the jQuery nested:field* events, and applications whose CSS matches a.add_nested_fields now that both controls are buttons. Refs #3723 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mshibuya
force-pushed
the
nested-form/replacement
branch
from
October 8, 2026 13:37
6e499d0 to
d31789f
Compare
mshibuya
marked this pull request as ready for review
October 10, 2026 13:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the
nested_formdependency with RailsAdmin's own implementation. Closes #3723.nested_formhas been archived since 2021. This reimplements what RailsAdmin used it for without reading its source: the contract was reconstructed from RailsAdmin's own call sites and from the HTML a rendered form actually contains, so there is no derivative work here. The vendored copy of its jQuery plugin is removed unopened.What this means for applications
An application that has not customized the nested form markup has nothing to do.
nested_form false, thenestedsection andinline_addall behave as before, and so does the whole server-side parameter path.The class names are unchanged too —
.fields,.add_nested_fields,.remove_nested_fields,.tab-pane,.object-infos,data-nestedmany,data-nestedone— so existing styling keeps working, with one exception: both controls are now<button type="button">rather than<a href="javascript:void(0)">, so a selector written asa.add_nested_fieldsneeds theadropped. In exchange, RailsAdmin's forms no longer need ajavascript:URL allowed in your Content Security Policy.A custom
_form_nested_manyor_form_nested_onepartial needs one line changed:link_to_addandlink_to_removekeep their names and their arguments. Renaming them was considered and dropped: it would have cost every application that customized these partials a change, in return for nothing but tidier vocabulary. Both controls now decide for themselves whether to appear, so the conditions around them can go —#nested_fields_foris the only method this adds to the public surface.Code that hooked the JavaScript needs updating:
window.nestedFormEventsand the jQuerynested:fieldAdded/nested:fieldRemovedevents are replaced byrails_admin.nested_field_addedandrails_admin.nested_field_removed,CustomEvents dispatched on the row and carrying{field, association}and{field, group, destroyed}.All of this is in docs/upgrading-to-4.md.
How it works now
A nested association used to be cloned from an HTML string, HTML-escaped into a
data-blueprintattribute on a page-global<div>emitted after the form. It now renders an inert<template>as the last child of its own.tab-content, and the add button finds it by name within its control group.Position carries what the global id was encoding, which is where most of the value is: nothing page-global is left to collide, a child's template rides along inside its parent's, and the markup is escaped once by ERB rather than escaped into an attribute and unescaped again by the browser.
Fixed along the way
allow_destroy: false.recursively_embeds_many, or a self-referentialhas_many— hung the request, climbing past 560 MB resident in seven minutes. One association may now repeat at mostMAX_NESTED_FORM_RECURSIONlevels down a chain; a chain of distinct models stays unlimited.recursively_embeds_*had no integration coverage at all before.Verification
The three ActionView methods this hooks into are byte-identical between 7.0.8.4 and 8.1.3.1, so there is no version spread across
>= 7.0, < 9.🤖 Generated with Claude Code