Probed at main 23b21b7, Linux x86-64, gcc 16.2.1.
Fixture = fixtures/tiny-api from roundhouse's own repo, with gadgets_controller.rb added as
app/controllers/gadgets_controller.rb, show.json.jbuilder and _gadget.json.jbuilder added
under app/views/gadgets/, and routes.rb replacing config/routes.rb (adds resources :gadgets, only: %i[show], otherwise unchanged). The locals key here deliberately matches the partial's own basename (gadget),
so the parameter-naming issue from #539 does not also fire — this fixture isolates the ivar read:
# app/controllers/gadgets_controller.rb
def show
@widget = Widget.find_by(id: params[:id])
@note = "hello"
end
# app/views/gadgets/show.json.jbuilder
json.partial!("gadgets/gadget", gadget: @widget)
# app/views/gadgets/_gadget.json.jbuilder
json.name(gadget.name)
json.note(@note) if @note
Real Rails renders this as {"name":"Sprocket","note":"hello"} — Rails partials see the controller's instance
variables (@note reaches _gadget.json.jbuilder the same way it reaches any other view/partial in the same
request), with no need to pass it as a local.
roundhouse --target spinel (and --target ruby) refuse to emit at all, with no --allow-unsupported:
app/views/gadgets/_gadget.json.jbuilder:2:21: error[ivar_unresolved]: @note has no known type
app/views/gadgets/_gadget.json.jbuilder:2:11: error[ivar_unresolved]: @note has no known type
With --allow-unsupported both targets emit (app/views/gadgets/_gadget_json.rb):
module Views
module Gadgets
def self.gadget_json(gadget)
io = String.new
io << "{"
io << "\"name\":"
io << JsonBuilder.encode_value(gadget.name)
if note
io << ","
io << "\"note\":"
io << JsonBuilder.encode_value(note)
end
io << "}"
io
end
end
end
@note became a bare note that the method never binds — not a parameter, not a local assignment anywhere in
the body. Running the --target ruby tree against a seeded widget row (REQUEST_METHOD=GET PATH_INFO=/gadgets/<id> ruby main.rb) raises on the first request that reaches the if note line:
app/views/gadgets/_gadget_json.rb:8:in 'Views::Gadgets.gadget_json': undefined local variable or method 'note' for module Views::Gadgets (NameError)
if note
^^^^
Did you mean? not
from app/views/gadgets/show_json.rb:7:in 'Views::Gadgets.show_json'
from app/controllers/gadgets_controller.rb:21:in 'GadgetsController#show'
Expected: @note resolves inside the partial the same way it would in Rails — either genuinely plumbed through
(the controller's ivar reaching the partial method, e.g. as an extra parameter the same way index/show templates
already receive their ivar-derived args) or, short of that, a survey/unsupported diagnostic at transpile
time instead of a confident wrong emit that only fails at request time.
Cause: src/lower/jbuilder_to_library/mod.rs, jbuilder_method_parts (around line 315):
let ivar_params: Vec<Symbol> = if is_partial {
Vec::new()
} else {
crate::lower::view_to_library::view_read_ivars(&view.body)
};
The surrounding comment states the assumption driving this: "A PARTIAL keeps the convention: its record arrives
positionally from the parent's collection, and it reads no ivar at all." For a non-partial template,
view_read_ivars walks the body and turns every @ivar read into a parameter; for a partial this is
unconditionally skipped, so any @ivar a partial body does read is still rewritten to a bare local by
rewrite_ivars_to_locals (the same ivar→local pass runs regardless of is_partial) but is never added as a
parameter anywhere — the two passes disagree about whether partials can read ivars at all. The strict-mode
error[ivar_unresolved] is the type checker catching the output of that disagreement, not a separate bug.
Duplicate search: gh search issues -R rubys/roundhouse for "jbuilder partial instance variable", "jbuilder ivar undefined", "partial @note", "jbuilder partial ivar scope", "json.partial! @" — no match other than
the unrelated json.partial! @record positional-record case (#322, closed, fixed by merged PR #356) and #360 /
#323 (open, different jbuilder shapes: a runtime-built partial path, and a collection+block/array!/
helper_method cluster). None covers an ivar read inside a partial's own body. PRs touching
jbuilder_to_library (#355, #359, #361, #367, #356 — all merged) address collection blocks, template locals,
conditionals, rescue blocks and the @record positional case; none touches ivar visibility inside partials.
Not a duplicate.
Fixture files
_gadget.json.jbuilder:
json.name(gadget.name)
json.note(@note) if @note
gadgets_controller.rb:
class GadgetsController < ApplicationController
def show
@widget = Widget.find_by(id: params[:id])
@note = "hello"
end
end
routes.rb:
Rails.application.routes.draw do
resources :widgets, only: %i[index show create update]
resources :gadgets, only: %i[show]
get "/feed.json", to: "formats#literal"
get "/formats", to: "formats#index"
get "/formats/:id", to: "formats#show"
end
show.json.jbuilder:
json.partial!("gadgets/gadget", gadget: @widget)
Found while compiling a Rails API app with --target spinel. Happy to send a PR for this if the direction above sounds right.
Probed at main 23b21b7, Linux x86-64, gcc 16.2.1.
Fixture =
fixtures/tiny-apifrom roundhouse's own repo, withgadgets_controller.rbadded asapp/controllers/gadgets_controller.rb,show.json.jbuilderand_gadget.json.jbuilderaddedunder
app/views/gadgets/, androutes.rbreplacingconfig/routes.rb(addsresources :gadgets, only: %i[show], otherwise unchanged). The locals key here deliberately matches the partial's own basename (gadget),so the parameter-naming issue from #539 does not also fire — this fixture isolates the ivar read:
Real Rails renders this as
{"name":"Sprocket","note":"hello"}— Rails partials see the controller's instancevariables (
@notereaches_gadget.json.jbuilderthe same way it reaches any other view/partial in the samerequest), with no need to pass it as a local.
roundhouse --target spinel(and--target ruby) refuse to emit at all, with no--allow-unsupported:With
--allow-unsupportedboth targets emit (app/views/gadgets/_gadget_json.rb):@notebecame a barenotethat the method never binds — not a parameter, not a local assignment anywhere inthe body. Running the
--target rubytree against a seeded widget row (REQUEST_METHOD=GET PATH_INFO=/gadgets/<id> ruby main.rb) raises on the first request that reaches theif noteline:Expected:
@noteresolves inside the partial the same way it would in Rails — either genuinely plumbed through(the controller's ivar reaching the partial method, e.g. as an extra parameter the same way index/show templates
already receive their ivar-derived args) or, short of that, a
survey/unsupporteddiagnostic at transpiletime instead of a confident wrong emit that only fails at request time.
Cause:
src/lower/jbuilder_to_library/mod.rs,jbuilder_method_parts(around line 315):The surrounding comment states the assumption driving this: "A PARTIAL keeps the convention: its record arrives
positionally from the parent's collection, and it reads no ivar at all." For a non-partial template,
view_read_ivarswalks the body and turns every@ivarread into a parameter; for a partial this isunconditionally skipped, so any
@ivara partial body does read is still rewritten to a bare local byrewrite_ivars_to_locals(the same ivar→local pass runs regardless ofis_partial) but is never added as aparameter anywhere — the two passes disagree about whether partials can read ivars at all. The strict-mode
error[ivar_unresolved]is the type checker catching the output of that disagreement, not a separate bug.Duplicate search:
gh search issues -R rubys/roundhousefor"jbuilder partial instance variable","jbuilder ivar undefined","partial @note","jbuilder partial ivar scope","json.partial! @"— no match other thanthe unrelated
json.partial! @recordpositional-record case (#322, closed, fixed by merged PR #356) and #360 /#323 (open, different jbuilder shapes: a runtime-built partial path, and a collection+block/
array!/helper_methodcluster). None covers an ivar read inside a partial's own body. PRs touchingjbuilder_to_library(#355, #359, #361, #367, #356 — all merged) address collection blocks, template locals,conditionals, rescue blocks and the
@recordpositional case; none touches ivar visibility inside partials.Not a duplicate.
Fixture files
_gadget.json.jbuilder:gadgets_controller.rb:routes.rb:show.json.jbuilder:Found while compiling a Rails API app with
--target spinel. Happy to send a PR for this if the direction above sounds right.