pbbuilder 0.6.0 → 0.7.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: 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