I have this line in my jbuilder template using the new hash syntax. It seems this line is not executed at all.
json.partial! 'comments', post:, comments:
If I modify the line back to the old syntax or add braces or brackets, it works.
json.partial!('comments', post:, comments:)
json.partial! 'comments', { post:, comments: }
json.partial! 'comments', post: post, comments: comments
Can anyone explain what happens here? Is it something related to the template compilation? Thanks.
Test template for simplicity:
# app/views/test.json.jbuilder
json.attr name:
I don't know how to get to it any other way but you can inspect compiled source
at:
https://github.com/rails/rails/blob/v7.0.4.3/actionview/lib/action_view/template.rb#L282
to see what code actually runs when template renders:
>> ApplicationController.renderer.render(template: "test", locals: {name: "first"})
Rendering test.json.jbuilder
" def _app_views_test_json_jbuilder__1585277848127427767_29960(local_assigns, output_buffer)\n @virtual_path = \"test\";name = local_assigns[:name]; name = name;;__already_defined = defined?(json); json||=JbuilderTemplate.new(self); json.attr name:\n\n json.target! unless (__already_defined && __already_defined != \"method\")\n end\n"
=> TypeError # doesn't actually render
formatted
def _app_views_test_json_jbuilder__1161877154070863274_90100(local_assigns, output_buffer)
@virtual_path = "test"; # ActionView
name = local_assigns[:name]; # ActionView
name = name;; # ActionView
__already_defined = defined?(json); # Jbuilder
json||=JbuilderTemplate.new(self); # Jbuilder
json.attr name: # Your template
json.target! unless (__already_defined && __already_defined != "method")
# ^ Jbuilder json output
end
If you don't have brackets around it, json.target!
becomes the value for the name
and your template returns whatever json.attr
returns or json.partial!
in your case.
It's also fixable if you put a semicolon at the end of this line:
https://github.com/rails/jbuilder/blob/v2.11.5/lib/jbuilder/jbuilder_template.rb#L277
# here v
%{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{source};
>> ApplicationController.renderer.render(template: "test", locals: {name: "first"})
=> "{\"attr\":{\"name\":\"first\"}}"
A simplified version
def json *args
@buffer = [*@buffer, *args]
end
def target
@buffer&.join
ensure
@buffer = nil
end
# ^ this is action view/jbuilder
# v this is jbuilder templates
def good_template name = "first"
json(name:)
target
end
# same as doing:
# `json name: target`
def bad_template name = "first"
json name:
target
end
>> good_template
=> "{:name=>\"first\"}"
>> bad_template
=> [{:name=>nil}] # Array instead of a String, if we had a controller
# there would be a TypeError somewhere.
So method hash:
is still an iffy syntax and much less obvious in a template. Just use parenthesis:
https://bugs.ruby-lang.org/issues/18396