From 26c236c9353f28b1ac9a5438dea34fcd340c6b6f Mon Sep 17 00:00:00 2001 From: Alexander Cederblad Date: Thu, 30 Apr 2026 12:27:05 +0200 Subject: [PATCH] Make method_missing private `alias_method :method_missing, :set!` was placed after the `private` directive, but `alias_method` does not respect the modifier, it copies the visibility of the source method. Since `#set!` is public, the alias silently made `method_missing` public, overriding the private one inherited from `BasicObject`. Add an explicit `private :method_missing` after the alias (above the `private` directive, for clarity) so that `#method_missing` is properly private. As a side effect, `json.method_missing "hello"` now reaches `#set!` with both a key and a value `set!(:method_missing, "hello")`, like any other DSL call, and produces `{"method_missing": "hello"}`. Before this patch the public alias was called directly as `set!("hello")`, a single-arg call that was silently dropped because `_set_value` short-circuits on a `BLANK` value, so the key never appeared in the output at all. Discovered by accident while migrating a Rails app to Jbuilder, where an object happened to expose `method_missing` as a key and produced unexpected output. --- lib/jbuilder.rb | 5 +++-- lib/jbuilder/jbuilder_template.rb | 5 +++-- test/jbuilder_template_test.rb | 5 +++++ test/jbuilder_test.rb | 8 ++++++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/jbuilder.rb b/lib/jbuilder.rb index 1290839..6602bfe 100644 --- a/lib/jbuilder.rb +++ b/lib/jbuilder.rb @@ -272,9 +272,10 @@ def target! @attributes.to_json end - private - alias_method :method_missing, :set! + private :method_missing + + private def _extract(object, attributes) if ::Hash === object diff --git a/lib/jbuilder/jbuilder_template.rb b/lib/jbuilder/jbuilder_template.rb index c2c54f2..00ae5b8 100644 --- a/lib/jbuilder/jbuilder_template.rb +++ b/lib/jbuilder/jbuilder_template.rb @@ -141,9 +141,10 @@ def set!(name, object = BLANK, *args) end end - private - alias_method :method_missing, :set! + private :method_missing + + private def _render_partial_with_options(options) options[:locals] ||= options.except(:partial, :as, :collection, :cached) diff --git a/test/jbuilder_template_test.rb b/test/jbuilder_template_test.rb index 76368e1..52e00c1 100644 --- a/test/jbuilder_template_test.rb +++ b/test/jbuilder_template_test.rb @@ -39,6 +39,11 @@ class JbuilderTemplateTest < ActiveSupport::TestCase assert_equal "hello", result["content"] end + test "method_missing can be used as a key" do + result = render('json.method_missing "hello"') + assert_equal({ "method_missing" => "hello" }, result) + end + test "partial by name with top-level locals" do result = render('json.partial! "partial", content: "hello"') assert_equal "hello", result["content"] diff --git a/test/jbuilder_test.rb b/test/jbuilder_test.rb index c01fb1e..3dd563f 100644 --- a/test/jbuilder_test.rb +++ b/test/jbuilder_test.rb @@ -61,6 +61,14 @@ class JbuilderTest < ActiveSupport::TestCase assert_equal 'hello', result['content'] end + test 'method_missing key' do + result = jbuild do |json| + json.method_missing 'hello' + end + + assert_equal 'hello', result['method_missing'] + end + test 'single key with false value' do result = jbuild do |json| json.content false