fix: render batched components as HTML - #12
Conversation
The batch endpoint is requested with a JSON Accept header, so Rails resolved partials against the JSON format, raised ActionView::MissingTemplate, and the controller converted that into a 404 for any application whose components are ordinary .html.erb partials. Render component partials with an explicit HTML format while the outer response stays JSON. Single-component refresh sends an HTML Accept header and was never affected. Also pass registrations to component_authorization_context, one for a single refresh and all of them for a batch, so applications no longer decode params[:tokens] by hand; callbacks accepting only controller: are detected and called unchanged.
Greptile SummaryThe PR fixes batched rendering of ordinary HTML partials under JSON requests and extends authorization callbacks with registration context while preserving existing callback shapes.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Reviews (2): Last reviewed commit: "fix: detect registrations on callable ob..." | Re-trigger Greptile |
| def accepts_registrations?(callable) | ||
| return false unless callable.respond_to?(:parameters) | ||
|
|
||
| callable.parameters.any? do |type, name| | ||
| type == :keyrest || (%i[key keyreq].include?(type) && name == :registrations) | ||
| end | ||
| end |
There was a problem hiding this comment.
Callable objects lose registrations
When configuration uses a callable object whose call method requires registrations:, capability detection inspects the object rather than its method and invokes it with only controller:, causing an ArgumentError and a server error for component refreshes.
| def accepts_registrations?(callable) | |
| return false unless callable.respond_to?(:parameters) | |
| callable.parameters.any? do |type, name| | |
| type == :keyrest || (%i[key keyreq].include?(type) && name == :registrations) | |
| end | |
| end | |
| def accepts_registrations?(callable) | |
| parameters = callable.respond_to?(:parameters) ? callable.parameters : callable.method(:call).parameters | |
| parameters.any? do |type, name| | |
| type == :keyrest || (%i[key keyreq].include?(type) && name == :registrations) | |
| end | |
| end |
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/controllers/solid_objects/components_controller.rb
Line: 147-153
Comment:
**Callable objects lose registrations**
When configuration uses a callable object whose `call` method requires `registrations:`, capability detection inspects the object rather than its method and invokes it with only `controller:`, causing an `ArgumentError` and a server error for component refreshes.
```suggestion
def accepts_registrations?(callable)
parameters = callable.respond_to?(:parameters) ? callable.parameters : callable.method(:call).parameters
parameters.any? do |type, name|
type == :keyrest || (%i[key keyreq].include?(type) && name == :registrations)
end
end
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Confirmed and fixed in 0d04645. A Proc answers parameters; a callable object does not, so my detection fell through to the controller:-only path and an object whose call requires registrations: raised ArgumentError. This is not hypothetical for this repository, which already accepts callable objects for configuration, as component_path_resolver does.
Parameters are now read from the call method when the object does not expose them directly, with a fallback to an empty list so an object exposing neither cannot raise here.
Two tests, one per shape: a callable object requiring registrations: receives them, and one accepting only controller: keeps working. I verified the first genuinely catches the defect by stashing the fix, where it fails, and restoring it, where it passes.
Capability detection asked the configured object for its parameters, which a lambda answers but a callable object does not, so an object whose call method requires registrations: was invoked with only controller: and raised ArgumentError. Read parameters from the call method when the object does not expose them directly. This repository already accepts callable objects for configuration, as component_path_resolver does.
|
@greptileai review |
Fixes the batch rendering bug you reported, and improves the authorization API for batching.
The bug
Confirmed exactly as reported. The browser module requests the batch with
Accept: application/json,so Rails resolved component partials against the JSON format, raised
ActionView::MissingTemplate,and the controller converted that into
UnknownComponentand a 404. Any application whosecomponents are ordinary
.html.erbpartials, which is all of them, got a 404 for every batch.Worth noting how it hid: my first version of the regression test passed.
ActionController::TestCasedefaults the request format to HTML, so the test did not reproduce what the browser actually sends.
Adding the
Accept: application/jsonheader reproduced the 404 with an empty body immediately. Thetests now set that header, which is what makes them regression tests rather than decoration.
The fix
ComponentRendererrenders partials with an explicitformats: [ :html ]. The outer response isstill JSON with the same envelope. Applications do not need
.json.erbcopies of anything.Fixing it in the renderer rather than the batch action means both endpoints resolve templates the
same way, so a future caller cannot reintroduce the bug by rendering components under a different
format.
Single-component refresh sends an HTML
Acceptheader and was never affected; a test asserts itstill returns
text/htmlwith rendered markup.Authorization API
component_authorization_contextnow receivesregistrations:alongsidecontroller:— oneregistration for a single refresh, all of them for a batch — so applications no longer inspect
params[:tokens]by hand:Backward compatible. The controller inspects the callable's parameters and passes the extra
keyword only to callables that declare it, either as
registrations:or a keyword splat. Acallback accepting only
controller:is called exactly as before. Tests cover all three shapes.Unchanged behaviour, all asserted
refresh_method, target IDs, per-frame revisions, authorization, and stale-revision handling areuntouched. Missing component still 404, invalid token still 400, unauthorized still 403, newer
requested revision still 409.
Tests
14 controller tests: an HTML ERB component through the JSON endpoint, several components in one
batch, a component with locals and dependencies, HTML rendering while the request format is JSON,
frame metadata, the four error cases, the three authorization callback shapes, and single-component
refresh still rendering HTML.
Upgrade notes
No database change, no migration, no initializer regeneration, no token format change. Applications
on 0.7.0 or 0.7.1 using
batch:should upgrade, since batching returns 404 for HTML partials onthose versions. No application code change is required; adopting
registrations:is optional.Validation
Standard Ruby, RuboCop, RBS, Steep, and Brakeman clean. Version bumped to 0.7.2.