Why the json.partial! line in jbuiler template is not executed when passing locals using new hash syntax?

95 Views Asked by At

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.

1

There are 1 best solutions below

0
Alex On BEST ANSWER

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