Skip to content

fix: Preserve locals when rendering inline partial for object#613

Open
moberegger wants to merge 3 commits intorails:mainfrom
affinity:moberegger/fix-inline-partial-locals-be
Open

fix: Preserve locals when rendering inline partial for object#613
moberegger wants to merge 3 commits intorails:mainfrom
affinity:moberegger/fix-inline-partial-locals-be

Conversation

@moberegger
Copy link
Copy Markdown
Contributor

@moberegger moberegger commented Apr 3, 2026

Ran into an odd bug. Given a template like

# locals (json:, foo:, my_local: 'default')

json.extract! foo, :id, :name
json.myLocal my_local

Rendering to a collection inline like

json.foos, @foos, partial: 'foos/foo', as: :foo, my_local: 'custom'

Would correctly render with the value of my_local with the value of 'custom'

{
  "foos": [
    "id": 1,
    "name": "Test",
    "myLocal": "custom"
  ]
}

But rendering a single object wasn't picking up the value for the local

json.foo, @foo, partial: 'foos/foo', as: :foo, my_local: 'custom'
{
  "foo": {
    "id": 1,
    "name": "Test",
    "myLocal": "default"
  }
}

If the partial didn't have a default value set for strict locals

# locals (json:, foo:, my_local:)

json.extract! foo, :id, :name
json.myLocal my_local

The following

json.foo, @foo, partial: 'foos/foo', as: :foo, my_local: 'custom'

would raise

ActionView::Template::Error: missing local: :my_local for app/views/foos/_foo.json.jbuilder...

The reason is because this render path in _set_inline_partial would set a key for locals before the render is done in _render_partial_with_options. _render_partial_with_options attempts to pluck key args for locals, but since the key is already there, they are effectively ignored.

This may have been a regression introduced by #591 which made it no longer reverse_merge! on the locals key. I haven't verified this, though.

The fix was to not have _set_inline_partial set locals, and instead defer that logic to _render_partial_with_options.

I added some extra tests cases to cover this.

  • A test for rendering an object to an inline partial (there were no tests for this)
  • A test for rendering an object to an inline partial with locals
  • A test for rendering a collection to an inline partial with locals

I also updated the docs to include an example for rendering an object to an inline partial. This is supported by the library, but was seemingly undocumented. Also documented rendering to partials inline with other locals, which was also supported by the library but seemingly undocumented.

As an aside: With some small refactoring, I think we can drop the need for the intermediary _set_inline_partial method. It doesn't do too much and I think the coordination of locals can all be handled within _render_partial_with_options. This would save a bit on indirection and also align everything to the same render path. Outside of the scope of this PR, but perhaps something I can try later.

else
_scope do
options[:locals] = { options[:as] => object }
options[options[:as]] = object
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the crux of the fix.

Comment on lines -181 to -184
def _render_partial(options)
options[:locals][:json] = self
@context.render options
end
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this only had one call site...

Comment on lines +177 to +178
options[:locals][:json] = self
@context.render options
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... I just inlined it here.

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