pbbuilder 0.6.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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