From 517c1e7f54a509446958c91d5ea99c25a8a4b8e5 Mon Sep 17 00:00:00 2001 From: Johny Ho Date: Wed, 14 Oct 2026 23:00:16 -0400 Subject: [PATCH] Send plain values straight to Base A `json.key value` went through Template#set!, BaseWithExtensions#set! and Base#set!, each checking for a block or options it did not have. Those layers were the largest cost in a render outside of Oj, more so without YJIT where every call is paid in full. Template#set! now hands a value with no block, that is not a Props::Options, to a new Base#set_value!, which writes it directly. Searcher#set_value! ignores it, as Searcher#set! already did for values. extract! uses set_value! too, and set_content! skips the extension lookups when there are no options. Co-Authored-By: Claude Opus 5.5 --- lib/props_template.rb | 4 +++- lib/props_template/base.rb | 17 ++++++++++++++++- lib/props_template/base_with_extensions.rb | 2 +- lib/props_template/searcher.rb | 4 ++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/props_template.rb b/lib/props_template.rb index 3ef6dd8..f213f40 100644 --- a/lib/props_template.rb +++ b/lib/props_template.rb @@ -67,8 +67,10 @@ def set!(key, options = {}, &block) @builder.item_context = found_item set!(key, found_options, &found_block) end - else + elsif block || options.is_a?(Props::Options) @builder.set!(key, options, &block) + else + @builder.set_value!(key, options) end end diff --git a/lib/props_template/base.rb b/lib/props_template/base.rb index 9380f4d..e4b957b 100644 --- a/lib/props_template/base.rb +++ b/lib/props_template/base.rb @@ -63,6 +63,21 @@ def set!(key, value = nil) nil end + def set_value!(key, value) + if @scope == :array + raise InvalidScopeForObjError.new("Attempted to set! on an array! scope") + end + + if @scope.nil? + @scope = :object + @stream.push_object + end + + @stream.push_value(value, format_key(key)) + + nil + end + def handle_collection_item(collection, item, index, options) @item_context = item @@ -124,7 +139,7 @@ def extract!(object, *values) [value, value] end - set!( + set_value!( key, object.is_a?(Hash) ? object.fetch(attribute) : object.public_send(attribute) ) diff --git a/lib/props_template/base_with_extensions.rb b/lib/props_template/base_with_extensions.rb index 2a7d1d8..f36ef85 100644 --- a/lib/props_template/base_with_extensions.rb +++ b/lib/props_template/base_with_extensions.rb @@ -27,7 +27,7 @@ def traveled_path! end def set_content!(options = {}) - return super if !@em.has_extensions(options) + return super if options.empty? || !@em.has_extensions(options) @em.handle(options, item_context) do yield diff --git a/lib/props_template/searcher.rb b/lib/props_template/searcher.rb index 578be9e..f8c00e9 100644 --- a/lib/props_template/searcher.rb +++ b/lib/props_template/searcher.rb @@ -46,6 +46,10 @@ def set_content!(*args) yield end + def set_value!(key, value) + nil + end + def set!(key, options = {}, &block) return if @found_block || !block && !options.is_a?(Props::Options)