Skip to content

Optimize memory allocation when rendering partials #591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions lib/jbuilder/jbuilder_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ def array!(collection = [], *args)
options = args.first

if args.one? && _partial_options?(options)
partial! options.merge(collection: collection)
options[:collection] = collection
partial! options
else
super
end
Expand All @@ -137,14 +138,14 @@ def set!(name, object = BLANK, *args)
private

def _render_partial_with_options(options)
options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
options.reverse_merge! ::JbuilderTemplate.template_lookup_options
options[:locals] ||= options.except(:partial, :as, :collection, :cached)
options[:handlers] ||= ::JbuilderTemplate.template_lookup_options[:handlers]
as = options[:as]

if as && options.key?(:collection) && CollectionRenderer.supported?
collection = options.delete(:collection) || []
partial = options.delete(:partial)
options[:locals].merge!(json: self)
options[:locals][:json] = self
collection = EnumerableCompat.new(collection) if collection.respond_to?(:count) && !collection.respond_to?(:size)

if options.has_key?(:layout)
Expand Down Expand Up @@ -173,9 +174,10 @@ def _render_partial_with_options(options)
locals = options.delete(:locals)
array! collection do |member|
member_locals = locals.clone
member_locals.merge! collection: collection
member_locals.merge! as => member
_render_partial options.merge(locals: member_locals)
member_locals[:collection] = collection
member_locals[as] = member
options[:locals] = member_locals
_render_partial options
end
else
array!
Expand All @@ -186,8 +188,8 @@ def _render_partial_with_options(options)
end

def _render_partial(options)
options[:locals].merge! json: self
@context.render options
options[:locals][:json] = self
@context.render options, nil
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The render helper in rails will default the second parameter to {}. By providing nil here we save on that extra memory allocation.

That second parameter is intended to be the options you provide to the partial if the first param is the partial name (ex: render 'foo', options). Since the partial name is included in the options, that second parameter isn't actually used.

end

def _cache_fragment_for(key, options, &block)
Expand Down Expand Up @@ -242,13 +244,19 @@ def _set_inline_partial(name, object, options)
value = if object.nil?
[]
elsif _is_collection?(object)
_scope{ _render_partial_with_options options.merge(collection: object) }
_scope do
options[:collection] = object
_render_partial_with_options options
end
else
locals = ::Hash[options[:as], object]
_scope{ _render_partial_with_options options.merge(locals: locals) }
_scope do
options[:locals] = locals
_render_partial_with_options options
end
end

set! name, value
_set_value name, value
Copy link
Author

@moberegger moberegger May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can set the value directly here instead of going back in through set! with different options. A call to set! with these parameters will just end up calling _set_value anyways.

This saves a bit in processing and also avoids an extra memory allocation for *args.

end

def _render_explicit_partial(name_or_options, locals = {})
Expand All @@ -259,7 +267,8 @@ def _render_explicit_partial(name_or_options, locals = {})
else
# partial! 'name', locals: {foo: 'bar'}
if locals.one? && (locals.keys.first == :locals)
options = locals.merge(partial: name_or_options)
locals[:partial] = name_or_options
options = locals
else
options = { partial: name_or_options, locals: locals }
end
Expand Down