pbbuilder 0.6.0 → 0.7.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: 83ca08d74d2463d2d14d5d57127e482a0ea9fde4a0cdcaac3f237c3a8f48ff23
4
+ data.tar.gz: 828fe17a1b1be0feab98d7cc68940350df6093d0995751e0873c46d9a363f487
5
5
  SHA512:
6
- metadata.gz: e7476c24846d78dd0dc011372998bb54301a673510ccc8cffe540714db1a991ba6b36c14ec0eb45572aef1880e9cb638870be38a7a510c4edb501eb0da670278
7
- data.tar.gz: 3027ae2509d083bd9da6a02bf9732c43038534992790017a3bdb00ba1bb80179d650d1a12d6f136af036854a5944125cad64d7b17268b084609f3642e8f7dbfc
6
+ metadata.gz: 2c4285699991af59101b07e3e04dd987620065362febdc8623d62390b15a17fd2660031f9e3a4cc3eb602609504ab70cde82848012fc061759124a706809fd3a
7
+ data.tar.gz: 214263e610fc46ab2bf978f17dd3dc8633ae757af8859906c4c588827c00e383b542ac749b5f9e089ede8401153a54629b82e83a29d5c212043965c70035fc58
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
@@ -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
@@ -0,0 +1,93 @@
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 assume a collection is being rendered with a partial for every element
31
+ # pb.friends @friends, partial: "friend", as: :friend
32
+ if args.one? && _partial_options?(kwargs)
33
+ # Call set! on the super class, passing in a block that renders a partial for every element
34
+ super(field, *args) do |element|
35
+ _set_inline_partial(element, kwargs)
36
+ end
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def _partial_options?(options)
45
+ ::Hash === options && options.key?(:as) && options.key?(:partial)
46
+ end
47
+
48
+ def _is_active_model?(object)
49
+ object.class.respond_to?(:model_name) && object.respond_to?(:to_partial_path)
50
+ end
51
+
52
+ def _render_explicit_partial(name_or_options, locals = {})
53
+ case name_or_options
54
+ when ::Hash
55
+ # partial! partial: 'name', foo: 'bar'
56
+ options = name_or_options
57
+ else
58
+ # partial! 'name', locals: {foo: 'bar'}
59
+ options = if locals.one? && (locals.keys.first == :locals)
60
+ locals.merge(partial: name_or_options)
61
+ else
62
+ {partial: name_or_options, locals: locals}
63
+ end
64
+ # partial! 'name', foo: 'bar'
65
+ as = locals.delete(:as)
66
+ options[:as] = as if as.present?
67
+ options[:collection] = locals[:collection] if locals.key?(:collection)
68
+ end
69
+
70
+ _render_partial_with_options options
71
+ end
72
+
73
+ def _render_active_model_partial(object)
74
+ @context.render object, pb: self
75
+ end
76
+
77
+ def _set_inline_partial(object, options)
78
+ locals = ::Hash[options[:as], object]
79
+ _render_partial_with_options options.merge(locals: locals)
80
+ end
81
+
82
+ def _render_partial_with_options(options)
83
+ options.reverse_merge! locals: options.except(:partial, :as, :collection)
84
+ options.reverse_merge! ::PbbuilderTemplate.template_lookup_options
85
+
86
+ _render_partial options
87
+ end
88
+
89
+ def _render_partial(options)
90
+ options[:locals][:pb] = self
91
+ @context.render options
92
+ end
93
+ end
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.7.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"
@@ -0,0 +1,97 @@
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 do
13
+ pb.partial! "racers/racer", racer: racer.best_friend
14
+ end if racer.best_friend.present?
15
+ PBBUILDER
16
+
17
+ PARTIALS = {
18
+ "_partial.pb.pbbuilder" => "pb.name name",
19
+ "_person.pb.pbbuilder" => PERSON_PARTIAL,
20
+ "racers/_racer.pb.pbbuilder" => RACER_PARTIAL,
21
+
22
+ # Ensure we find only Pbbuilder partials from within Pbbuilder templates.
23
+ "_person.html.erb" => "Hello world!"
24
+ }
25
+
26
+ test "basic template" do
27
+ result = render('pb.name "hello"')
28
+ assert_equal "hello", result.name
29
+ end
30
+
31
+ test "partial by name with top-level locals" do
32
+ result = render('pb.partial! "partial", name: "hello"')
33
+ assert_equal "hello", result.name
34
+ end
35
+
36
+ test "partial by name with nested locals" do
37
+ result = render('pb.partial! "partial", locals: { name: "hello" }')
38
+ assert_equal "hello", result.name
39
+ end
40
+
41
+ test "partial by options containing nested locals" do
42
+ result = render('pb.partial! partial: "partial", locals: { name: "hello" }')
43
+ assert_equal "hello", result.name
44
+ end
45
+
46
+ test "partial by options containing top-level locals" do
47
+ result = render('pb.partial! partial: "partial", name: "hello"')
48
+ assert_equal "hello", result.name
49
+ end
50
+
51
+ test "partial for Active Model" do
52
+ result = render("pb.partial! @racer", racer: Racer.new(123, "Chris Harris", []))
53
+ assert_equal "Chris Harris", result.name
54
+ end
55
+
56
+ test "collection partial" do
57
+ friends = [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
58
+ result = render("pb.partial! @racer", racer: Racer.new(123, "Chris Harris", friends))
59
+ assert_equal 2, result.friends.size
60
+ assert_equal "Johnny Test", result.friends[0].name
61
+ assert_equal "Max Verstappen", result.friends[1].name
62
+ end
63
+
64
+ test "nested message partial" do
65
+ other_racer = Racer.new(2, "Max Verstappen", [])
66
+ result = render("pb.partial! @racer", racer: Racer.new(123, "Chris Harris", [], other_racer))
67
+ assert_equal "Max Verstappen", result.best_friend.name
68
+ end
69
+
70
+ private
71
+
72
+ def render(*args)
73
+ render_without_parsing(*args)
74
+ end
75
+
76
+ def render_without_parsing(source, assigns = {})
77
+ view = build_view(fixtures: PARTIALS.merge("source.pb.pbbuilder" => source), assigns: assigns)
78
+ view.render(template: "source", handlers: [:pbbuilder], formats: [:pb])
79
+ end
80
+
81
+ def build_view(options = {})
82
+ resolver = ActionView::FixtureResolver.new(options.fetch(:fixtures))
83
+ lookup_context = ActionView::LookupContext.new([resolver], {}, [""])
84
+ controller = ActionView::TestCase::TestController.new
85
+
86
+ assigns = options.fetch(:assigns, {})
87
+ assigns.reverse_merge! _response_class: API::Person
88
+
89
+ view = ActionView::Base.with_empty_template_cache.new(lookup_context, assigns, controller)
90
+
91
+ def view.view_cache_dependencies
92
+ []
93
+ end
94
+
95
+ view
96
+ end
97
+ 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.7.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-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -24,7 +24,7 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.15.5
27
- description:
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.3
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