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

failing test for cached:true parameter in collections #42

Closed
wants to merge 3 commits 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
22 changes: 19 additions & 3 deletions lib/pbbuilder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ def method_missing(...)
set!(...)
end

def attributes!
@message.to_h
end

def respond_to_missing?(field)
[email protected](field.to_s)
end

def set!(field, *args, &block)
name = field.to_s
descriptor = @message.class.descriptor.lookup(name)
descriptor = _descriptor_for_field(name)
::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?

if block
Expand All @@ -72,10 +76,18 @@ def set!(field, *args, &block)
arg.to_hash.each do |k, v|
@message[name][k] = v
end
elsif arg.respond_to?(:to_ary)
elsif arg.respond_to?(:to_ary) && !descriptor.type.eql?(:message)
# pb.fields ["one", "two"]
# Using concat so it behaves the same as _append_repeated
@message[name].concat arg.to_ary
elsif arg.respond_to?(:to_ary) && descriptor.type.eql?(:message)
# pb.friends [Person.new(name: "Johnny Test"), Person.new(name: "Max Verstappen")]
# Concat another Protobuf message into parent Protobuf message (not ary of strings)

args.flatten.each do |obj|
# Creates a message from descriptor
@message[name].push descriptor.subtype.msgclass.new(obj)
end
else
# pb.fields "one"
@message[name].push arg
Expand Down Expand Up @@ -118,7 +130,7 @@ def merge!(object)
object.each_key do |key|
next if object[key].respond_to?(:empty?) && object[key].empty?

descriptor = @message.class.descriptor.lookup(key.to_s)
descriptor = _descriptor_for_field(key)
::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?

if descriptor.label == :repeated
Expand Down Expand Up @@ -184,6 +196,10 @@ def target!

private

def _descriptor_for_field(field)
@message.class.descriptor.lookup(field.to_s)
end

# Appends protobuf message with existing @message object
#
# @param name string
Expand Down
87 changes: 87 additions & 0 deletions lib/pbbuilder/collection_renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@

require 'active_support/concern'
require 'action_view'

begin
require 'action_view/renderer/collection_renderer'
rescue LoadError
require 'action_view/renderer/partial_renderer'
end

class Pbbuilder
module CollectionRenderable # :nodoc:
extend ActiveSupport::Concern

class_methods do
def supported?
superclass.private_method_defined?(:build_rendered_template) && self.superclass.private_method_defined?(:build_rendered_collection)
end
end

private

def build_rendered_template(content, template, layout = nil)
super(content || pb.attributes!, template)
end

def build_rendered_collection(templates, _spacer)
pb.set!(field, templates.map(&:body))
end

def pb
@options[:locals].fetch(:pb)
end

def field
@options[:locals].fetch(:field).to_s
end
end

if defined?(::ActionView::CollectionRenderer)
# Rails 6.1 support:
class CollectionRenderer < ::ActionView::CollectionRenderer # :nodoc:
include CollectionRenderable

def initialize(lookup_context, options, &scope)
super(lookup_context, options)
@scope = scope
end

private
def collection_with_template(view, template, layout, collection)
super(view, template, layout, collection)
end
end
else
# Rails 6.0 support:
class CollectionRenderer < ::ActionView::PartialRenderer # :nodoc:
include CollectionRenderable

def initialize(lookup_context, options, &scope)
super(lookup_context)
@options = options
@scope = scope
end

def render_collection_with_partial(collection, partial, context, block)
render(context, @options.merge(collection: collection, partial: partial), block)
end

private
def collection_without_template(view)
super(view)
end

def collection_with_template(view, template)
super(view, template)
end
end
end

class EnumerableCompat < ::SimpleDelegator
# Rails 6.1 requires this.
def size(*args, &block)
__getobj__.count(*args, &block)
end
end
end
8 changes: 1 addition & 7 deletions lib/pbbuilder/pbbuilder.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
# frozen_string_literal: true

Pbbuilder = Class.new(begin
require 'active_support/proxy_object'
ActiveSupport::ProxyObject
rescue LoadError
require 'active_support/basic_object'
ActiveSupport::BasicObject
end)
Pbbuilder = Class.new(Object)
19 changes: 19 additions & 0 deletions lib/pbbuilder/template.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'pbbuilder/collection_renderer'

# PbbuilderTemplate is an extension of Pbbuilder to be used as a Rails template
# It adds support for partials.
class PbbuilderTemplate < Pbbuilder
Expand Down Expand Up @@ -37,6 +39,23 @@ def set!(field, *args, **kwargs, &block)
super(field, *args) do |element|
_set_inline_partial(element, kwargs)
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]
options[:locals].merge!(pb: self)
options[:locals].merge!(field: field)

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

else
# pb.best_friend partial: "person", person: @best_friend
# Call set! as a submessage, passing in the kwargs as partial options
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 @@ -30,11 +30,26 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
assert_equal "hello", result.name
end

test "collections render" do
result = render('pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')

assert_equal 2, result.friends.count
end

test "partial by name with top-level locals" do
result = render('pb.partial! "partial", name: "hello"')
assert_equal "hello", result.name
end

test "partial by name with caching" do
skip()
assert_difference('Rails.cache.instance_variable_get(:@data).size') do
result = render('pb.partial! "partial", name: "hello", cached: true')

assert_equal "hello", result.name
end
end

test "submessage partial" do
other_racer = Racer.new(2, "Max Verstappen", [])
racer = Racer.new(123, "Chris Harris", [], other_racer)
Expand Down