Skip to content
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

fragment caching implementation #49

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
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
71 changes: 40 additions & 31 deletions lib/pbbuilder/template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ def partial!(*args)
end
end

# Set value in a field in message.
#
# @example
# pb.friends @friends, partial: "friend", as: :friend
# pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
# pb.best_friend partial: "person", person: @best_friend

def set!(field, *args, **kwargs, &block)
# If partial options are being passed, we render a submessage with a partial
if kwargs.has_key?(:partial)
Expand All @@ -41,37 +48,7 @@ def set!(field, *args, **kwargs, &block)
end
elsif kwargs.has_key?(:collection) && kwargs.has_key?(:as)
# pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
# collection renderer
options = kwargs.deep_dup

options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
options.reverse_merge! ::PbbuilderTemplate.template_lookup_options

collection = options[:collection] || []
partial = options[:partial]

# The way recursive rendering works is that CollectionRenderer needs to be aware of node its currently rendering and parent node,
# these is no need to know entire "stack" of nodes. CollectionRenderer would traverse to bottom node render that first and then go up in stack.

# CollectionRenderer uses locals[:pb] to render the partial as a protobuf message,
# but also needs locals[:pb_parent] to apply rendered partial to top level protobuf message.

# This logic could be found in CollectionRenderer#build_rendered_collection method that we over wrote.
options[:locals].merge!(pb: ::PbbuilderTemplate.new(@context, new_message_for(field)))
options[:locals].merge!(pb_parent: self)
options[:locals].merge!(field: field)

if options.has_key?(:layout)
raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
end

if options.has_key?(:spacer_template)
raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
end

CollectionRenderer
.new(@context.lookup_context, options) { |&block| _scope(message[field.to_s],&block) }
.render_collection_with_partial(collection, partial, @context, nil)
_render_collection_with_options(field, kwargs[:collection], kwargs.deep_dup)
else
# pb.best_friend partial: "person", person: @best_friend
# Call set! as a submessage, passing in the kwargs as partial options
Expand Down Expand Up @@ -119,6 +96,38 @@ def cache_if!(condition, *args, &block)

private

# Uses ActionView::CollectionRenderer to render collection effectively (and users fragment caching)
#
# The way recursive rendering works is that CollectionRenderer needs to be aware of node its currently rendering and parent node,
# these is no need to know entire "stack" of nodes. CollectionRenderer would traverse to bottom node render that first and then go up in stack.

# CollectionRenderer uses locals[:pb] to render the partial as a protobuf message,
# but also needs locals[:pb_parent] to apply rendered partial to top level protobuf message.

# This logic could be found in CollectionRenderer#build_rendered_collection method that we over wrote.
def _render_collection_with_options(field, collection, options)
partial = options[:partial]

options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
options.reverse_merge! ::PbbuilderTemplate.template_lookup_options

options[:locals].merge!(pb: ::PbbuilderTemplate.new(@context, new_message_for(field)))
options[:locals].merge!(pb_parent: self)
options[:locals].merge!(field: field)

if options.has_key?(:layout)
raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
end

if options.has_key?(:spacer_template)
raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
end

CollectionRenderer
.new(@context.lookup_context, options) { |&block| _scope(message[field.to_s],&block) }
.render_collection_with_partial(collection, partial, @context, nil)
end

# Writes to cache, if cache with keys is missing.
#
# @return fragment value
Expand Down
15 changes: 15 additions & 0 deletions test/pbbuilder_template_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
assert_equal "https://google.com/test3.svg", result.friends.first.friends.first.friends.first.logo.url
end

test "partial by name with caching" do
template = <<-PBBUILDER
racers = [Racer.new(1, "Johnny Test", [], nil, API::Asset.new(url: "https://google.com/test1.svg")), Racer.new(2, "Max Verstappen", [])]
pb.friends partial: "racers/racer", as: :racer, collection: racers, cached: true
PBBUILDER

assert_difference('Rails.cache.instance_variable_get(:@data).size') do
result = render(template)
end

assert_equal 2, result.friends.count
assert_nil result.logo
assert_equal "https://google.com/test1.svg", result.friends.first.logo.url
end

test "CollectionRenderer: raises an error on a render with :layout option" do
error = assert_raises NotImplementedError do
render('pb.friends partial: "racers/racer", as: :racer, layout: "layout", collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')
Expand Down
Loading