pbbuilder 0.6.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3f417c956ceb3a2123489021f4cc401b7bf56399355fb6bb0d4ebaa437d1cb2
4
- data.tar.gz: '0408c3174481e7aa7e22f129829a973f822a7245d57c3e93bad2ef564d3ee05f'
3
+ metadata.gz: cc495285bd745ade01ad6dc96698eff9ffdabf2a60154f8714a39d6f69ad1524
4
+ data.tar.gz: 30083b95fa612fbcb39df4b88bcb8af170e4d78a4b2ca407ab75bf30d767e990
5
5
  SHA512:
6
- metadata.gz: e7476c24846d78dd0dc011372998bb54301a673510ccc8cffe540714db1a991ba6b36c14ec0eb45572aef1880e9cb638870be38a7a510c4edb501eb0da670278
7
- data.tar.gz: 3027ae2509d083bd9da6a02bf9732c43038534992790017a3bdb00ba1bb80179d650d1a12d6f136af036854a5944125cad64d7b17268b084609f3642e8f7dbfc
6
+ metadata.gz: 649b1beb020c09e1f5167b8882d7fd8e6578f29c45f51fb807a6ff4a749e6dd0be0a76111e7947aaccf967ab8f6364bb1878eb31fcf7bd65e78d871851416461
7
+ data.tar.gz: ef254cdf2acd93e0dbdbf9d49c20653ec8f12b0700fe9853faf3eaa2ac1495bd3e8ff8a4ea3516e031acb88c0ebb859f4291fe6e82a8161aabd3ae5b6fb90d76
@@ -1,3 +1,5 @@
1
+ require "pbbuilder/template"
2
+
1
3
  # Basically copied and pasted from JbuilderHandler, except it uses Pbbuilder
2
4
 
3
5
  class PbbuilderHandler
@@ -6,7 +8,7 @@ class PbbuilderHandler
6
8
  def self.call(template, source = nil)
7
9
  source ||= template.source
8
10
  # We need to keep `source` on the first line, so line numbers are correct if there's an error
9
- %{pb=Pbbuilder.new(@_response_class.new); #{source}
10
- pb.target!}
11
+ %{__already_defined = defined?(pb); pb ||= PbbuilderTemplate.new(self, @_response_class.new); #{source}
12
+ pb.target! unless (__already_defined && __already_defined != "method")}
11
13
  end
12
14
  end
@@ -4,6 +4,7 @@ class Pbbuilder
4
4
  class Railtie < ::Rails::Railtie
5
5
  initializer :register_handler do
6
6
  ActiveSupport.on_load :action_view do
7
+ ActionView::Template::Types.symbols << :pb
7
8
  ActionView::Template.register_template_handler :pbbuilder, PbbuilderHandler
8
9
  end
9
10
  end
@@ -0,0 +1,97 @@
1
+ # PbbuilderTemplate is an extension of Pbbuilder to be used as a Rails template
2
+ # It adds support for partials.
3
+ class PbbuilderTemplate < Pbbuilder
4
+ class << self
5
+ attr_accessor :template_lookup_options
6
+ end
7
+
8
+ self.template_lookup_options = {handlers: [:pbbuilder]}
9
+
10
+ def initialize(context, message)
11
+ @context = context
12
+ super(message)
13
+ end
14
+
15
+ # Render a partial. Can be called as:
16
+ # pb.partial! "name/of_partial", argument: 123
17
+ # pb.partial! "name/of_partial", locals: {argument: 123}
18
+ # pb.partial! partial: "name/of_partial", argument: 123
19
+ # pb.partial! partial: "name/of_partial", locals: {argument: 123}
20
+ # pb.partial! @model # @model is an ActiveModel value, it will use the name to look up a partial
21
+ def partial!(*args)
22
+ if args.one? && _is_active_model?(args.first)
23
+ _render_active_model_partial args.first
24
+ else
25
+ _render_explicit_partial(*args)
26
+ end
27
+ end
28
+
29
+ def set!(field, *args, **kwargs, &block)
30
+ # If partial options are being passed, we render a submessage with a partial
31
+ if kwargs.has_key?(:partial)
32
+ if args.one? && kwargs.has_key?(:as)
33
+ # pb.friends @friends, partial: "friend", as: :friend
34
+ # Call set! on the super class, passing in a block that renders a partial for every element
35
+ super(field, *args) do |element|
36
+ _set_inline_partial(element, kwargs)
37
+ end
38
+ else
39
+ # pb.best_friend partial: "person", person: @best_friend
40
+ # Call set! as a submessage, passing in the kwargs as partial options
41
+ super(field, *args) do
42
+ _render_partial_with_options(kwargs)
43
+ end
44
+ end
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def _is_active_model?(object)
53
+ object.class.respond_to?(:model_name) && object.respond_to?(:to_partial_path)
54
+ end
55
+
56
+ def _render_explicit_partial(name_or_options, locals = {})
57
+ case name_or_options
58
+ when ::Hash
59
+ # partial! partial: 'name', foo: 'bar'
60
+ options = name_or_options
61
+ else
62
+ # partial! 'name', locals: {foo: 'bar'}
63
+ options = if locals.one? && (locals.keys.first == :locals)
64
+ locals.merge(partial: name_or_options)
65
+ else
66
+ {partial: name_or_options, locals: locals}
67
+ end
68
+ # partial! 'name', foo: 'bar'
69
+ as = locals.delete(:as)
70
+ options[:as] = as if as.present?
71
+ options[:collection] = locals[:collection] if locals.key?(:collection)
72
+ end
73
+
74
+ _render_partial_with_options options
75
+ end
76
+
77
+ def _render_active_model_partial(object)
78
+ @context.render object, pb: self
79
+ end
80
+
81
+ def _set_inline_partial(object, options)
82
+ locals = ::Hash[options[:as], object]
83
+ _render_partial_with_options options.merge(locals: locals)
84
+ end
85
+
86
+ def _render_partial_with_options(options)
87
+ options.reverse_merge! locals: options.except(:partial, :as, :collection)
88
+ options.reverse_merge! ::PbbuilderTemplate.template_lookup_options
89
+
90
+ _render_partial options
91
+ end
92
+
93
+ def _render_partial(options)
94
+ options[:locals][:pb] = self
95
+ @context.render options
96
+ end
97
+ end
data/lib/pbbuilder.rb CHANGED
@@ -28,8 +28,8 @@ class Pbbuilder < BasicObject
28
28
  yield self if ::Kernel.block_given?
29
29
  end
30
30
 
31
- def method_missing(field, *args, &block)
32
- set!(field, *args, &block)
31
+ def method_missing(...)
32
+ set!(...)
33
33
  end
34
34
 
35
35
  def respond_to_missing?(field)
@@ -39,19 +39,20 @@ class Pbbuilder < BasicObject
39
39
  def set!(field, *args, &block)
40
40
  name = field.to_s
41
41
  descriptor = @message.class.descriptor.lookup(name)
42
+ ::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?
42
43
 
43
44
  if block
44
- raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
45
+ ::Kernel.raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
45
46
 
46
47
  if descriptor.label == :repeated
47
48
  # pb.field @array { |element| pb.name element.name }
48
- raise ::ArgumentError, "wrong number of arguments (expected 1)" unless args.length == 1
49
+ ::Kernel.raise ::ArgumentError, "wrong number of arguments #{args.length} (expected 1)" unless args.length == 1
49
50
  collection = args.first
50
51
  _append_repeated(name, descriptor, collection, &block)
51
52
  return
52
53
  end
53
54
 
54
- raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty?
55
+ ::Kernel.raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty?
55
56
  # pb.field { pb.name "hello" }
56
57
  message = (@message[name] ||= _new_message_from_descriptor(descriptor))
57
58
  _scope(message, &block)
@@ -107,7 +108,7 @@ class Pbbuilder < BasicObject
107
108
  private
108
109
 
109
110
  def _append_repeated(name, descriptor, collection, &block)
110
- raise ::ArgumentError, "expected Enumerable" unless collection.respond_to?(:map)
111
+ ::Kernel.raise ::ArgumentError, "expected Enumerable" unless collection.respond_to?(:map)
111
112
  elements = collection.map do |element|
112
113
  message = _new_message_from_descriptor(descriptor)
113
114
  _scope(message) { block.call(element) }
@@ -126,7 +127,7 @@ class Pbbuilder < BasicObject
126
127
  end
127
128
 
128
129
  def _new_message_from_descriptor(descriptor)
129
- raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
130
+ ::Kernel.raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
130
131
 
131
132
  # Here we're using Protobuf reflection to create an instance of the message class
132
133
  message_descriptor = descriptor.subtype
data/pbbuilder.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "pbbuilder"
3
- spec.version = "0.6.0"
3
+ spec.version = "0.10.0"
4
4
  spec.authors = ["Bouke van der Bijl"]
5
5
  spec.email = ["bouke@cheddar.me"]
6
6
  spec.homepage = "https://github.com/cheddar-me/pbbuilder"
@@ -10,5 +10,5 @@ Gem::Specification.new do |spec|
10
10
  spec.files = `git ls-files`.split("\n")
11
11
  spec.test_files = `git ls-files -- test/*`.split("\n")
12
12
 
13
- spec.add_dependency "google-protobuf", "~> 3.15.5"
13
+ spec.add_dependency "google-protobuf", "~> 3.17.3"
14
14
  end
@@ -0,0 +1,107 @@
1
+ require "test_helper"
2
+ require "action_view/testing/resolvers"
3
+
4
+ class PbbuilderTemplateTest < ActiveSupport::TestCase
5
+ PERSON_PARTIAL = <<-PBBUILDER
6
+ pb.extract! person, :name
7
+ PBBUILDER
8
+
9
+ RACER_PARTIAL = <<-PBBUILDER
10
+ pb.extract! racer, :name
11
+ pb.friends racer.friends, partial: "racers/racer", as: :racer
12
+ pb.best_friend partial: "racers/racer", racer: racer.best_friend if racer.best_friend.present?
13
+ PBBUILDER
14
+
15
+ PARTIALS = {
16
+ "_partial.pb.pbbuilder" => "pb.name name",
17
+ "_person.pb.pbbuilder" => PERSON_PARTIAL,
18
+ "racers/_racer.pb.pbbuilder" => RACER_PARTIAL,
19
+
20
+ # Ensure we find only Pbbuilder partials from within Pbbuilder templates.
21
+ "_person.html.erb" => "Hello world!"
22
+ }
23
+
24
+ test "basic template" do
25
+ result = render('pb.name "hello"')
26
+ assert_equal "hello", result.name
27
+ end
28
+
29
+ test "partial by name with top-level locals" do
30
+ result = render('pb.partial! "partial", name: "hello"')
31
+ assert_equal "hello", result.name
32
+ end
33
+
34
+ test "submessage partial" do
35
+ other_racer = Racer.new(2, "Max Verstappen", [])
36
+ racer = Racer.new(123, "Chris Harris", [], other_racer)
37
+ result = render('pb.best_friend partial: "person", person: @racer.best_friend', racer: racer)
38
+ assert_equal "Max Verstappen", result.best_friend.name
39
+ end
40
+
41
+ test "hash" do
42
+ result = render('pb.favourite_foods "pizza" => "yes"')
43
+ assert_equal({"pizza" => "yes"}, result.favourite_foods.to_h)
44
+ end
45
+
46
+ test "partial by name with nested locals" do
47
+ result = render('pb.partial! "partial", locals: { name: "hello" }')
48
+ assert_equal "hello", result.name
49
+ end
50
+
51
+ test "partial by options containing nested locals" do
52
+ result = render('pb.partial! partial: "partial", locals: { name: "hello" }')
53
+ assert_equal "hello", result.name
54
+ end
55
+
56
+ test "partial by options containing top-level locals" do
57
+ result = render('pb.partial! partial: "partial", name: "hello"')
58
+ assert_equal "hello", result.name
59
+ end
60
+
61
+ test "partial for Active Model" do
62
+ result = render("pb.partial! @racer", racer: Racer.new(123, "Chris Harris", []))
63
+ assert_equal "Chris Harris", result.name
64
+ end
65
+
66
+ test "collection partial" do
67
+ friends = [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
68
+ result = render("pb.partial! @racer", racer: Racer.new(123, "Chris Harris", friends))
69
+ assert_equal 2, result.friends.size
70
+ assert_equal "Johnny Test", result.friends[0].name
71
+ assert_equal "Max Verstappen", result.friends[1].name
72
+ end
73
+
74
+ test "nested message partial" do
75
+ other_racer = Racer.new(2, "Max Verstappen", [])
76
+ result = render("pb.partial! @racer", racer: Racer.new(123, "Chris Harris", [], other_racer))
77
+ assert_equal "Max Verstappen", result.best_friend.name
78
+ end
79
+
80
+ private
81
+
82
+ def render(*args)
83
+ render_without_parsing(*args)
84
+ end
85
+
86
+ def render_without_parsing(source, assigns = {})
87
+ view = build_view(fixtures: PARTIALS.merge("source.pb.pbbuilder" => source), assigns: assigns)
88
+ view.render(template: "source", handlers: [:pbbuilder], formats: [:pb])
89
+ end
90
+
91
+ def build_view(options = {})
92
+ resolver = ActionView::FixtureResolver.new(options.fetch(:fixtures))
93
+ lookup_context = ActionView::LookupContext.new([resolver], {}, [""])
94
+ controller = ActionView::TestCase::TestController.new
95
+
96
+ assigns = options.fetch(:assigns, {})
97
+ assigns.reverse_merge! _response_class: API::Person
98
+
99
+ view = ActionView::Base.with_empty_template_cache.new(lookup_context, assigns, controller)
100
+
101
+ def view.view_cache_dependencies
102
+ []
103
+ end
104
+
105
+ view
106
+ end
107
+ end
data/test/test_helper.rb CHANGED
@@ -4,6 +4,15 @@ ENV["RAILS_ENV"] = "test"
4
4
  require "rails"
5
5
  require "rails/test_help"
6
6
  require "rails/test_unit/reporter"
7
+
8
+ require "active_support"
9
+ require "active_support/core_ext/array/access"
10
+ require "active_support/cache/memory_store"
11
+ require "active_support/json"
12
+ require "active_model"
13
+ require "action_view"
14
+ require "rails/version"
15
+
7
16
  require "pbbuilder"
8
17
 
9
18
  Rails::TestUnitReporter.executable = "bin/test"
@@ -27,3 +36,10 @@ end
27
36
  module API
28
37
  Person = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pbbuildertest.Person").msgclass
29
38
  end
39
+
40
+ class Racer < Struct.new(:id, :name, :friends, :best_friend)
41
+ extend ActiveModel::Naming
42
+ include ActiveModel::Conversion
43
+ end
44
+
45
+ ActionView::Template.register_template_handler :pbbuilder, PbbuilderHandler
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pbbuilder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bouke van der Bijl
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-05 00:00:00.000000000 Z
11
+ date: 2021-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -16,15 +16,15 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 3.15.5
19
+ version: 3.17.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 3.15.5
27
- description:
26
+ version: 3.17.3
27
+ description:
28
28
  email:
29
29
  - bouke@cheddar.me
30
30
  executables: []
@@ -42,8 +42,10 @@ files:
42
42
  - lib/pbbuilder/handler.rb
43
43
  - lib/pbbuilder/protobuf_extension.rb
44
44
  - lib/pbbuilder/railtie.rb
45
+ - lib/pbbuilder/template.rb
45
46
  - lib/tasks/pbbuilder_tasks.rake
46
47
  - pbbuilder.gemspec
48
+ - test/pbbuilder_template_test.rb
47
49
  - test/pbbuilder_test.rb
48
50
  - test/protobuf_extension_test.rb
49
51
  - test/test_helper.rb
@@ -51,7 +53,7 @@ homepage: https://github.com/cheddar-me/pbbuilder
51
53
  licenses:
52
54
  - MIT
53
55
  metadata: {}
54
- post_install_message:
56
+ post_install_message:
55
57
  rdoc_options: []
56
58
  require_paths:
57
59
  - lib
@@ -66,11 +68,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
68
  - !ruby/object:Gem::Version
67
69
  version: '0'
68
70
  requirements: []
69
- rubygems_version: 3.1.4
70
- signing_key:
71
+ rubygems_version: 3.2.22
72
+ signing_key:
71
73
  specification_version: 4
72
74
  summary: Generate Protobuf messages via a Builder-style DSL
73
75
  test_files:
76
+ - test/pbbuilder_template_test.rb
74
77
  - test/pbbuilder_test.rb
75
78
  - test/protobuf_extension_test.rb
76
79
  - test/test_helper.rb