pbbuilder 0.17.0 → 0.19.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: fe95820bf0015825b24715abe48a568f865affb04ec6c3db6261f33cd3e19829
4
- data.tar.gz: 0de697e6e0970a49cf599cb764e1941e019b1b6e0dddcc86c862c070a34ec904
3
+ metadata.gz: 1ea3598ad1ed31172d0b3154c6968649f63073f23de0d94abf191a07dbdbf4e0
4
+ data.tar.gz: 7285883ae069eeff4f1860eb9b4c4a727585e6762b3bfd974ce1fb9e51928086
5
5
  SHA512:
6
- metadata.gz: fa06d08bee0e966095491d7b1d0956ff5c0f9a5d305fe66308c98883ddbe3c424d60b62ebf846fe25d2e7ec86181baf0666ec0d638648117188d1b17e3185784
7
- data.tar.gz: dac52c94458493380247cba47db06e61ad29590ff8b09b51abbe870aca8381f553870c16604b8079653488c0acc76fa87bfa54cb6a652e3c9fd0538e09ed85d8
6
+ metadata.gz: e051d79882379fb30f3f0b4bab503e6591034d1cc16639918d685a0ef19e6f7cc42ea224954bf755db739f6a2b2c2c86a8c7666e23065ce337aafb862486b4f6
7
+ data.tar.gz: 90f2266161a9a101fd4b67afb71130a009b17ec9a5139dc583db18d02a1a32a2bd7730f1ea872c3d4b77a4d64f953b1a34e72cb35f845dc6a879f117f7c3c5ec
@@ -14,21 +14,12 @@ jobs:
14
14
  fail-fast: false
15
15
  matrix:
16
16
  ruby: ["3.0", "3.1", "3.2", "3.3"]
17
-
18
- gemfile: [ "rails_6_1", "rails_7_0"]
17
+ gemfile: [ "rails_6_1", "rails_7_0", "rails_7_2"]
18
+ exclude:
19
+ - ruby: "3.0"
20
+ gemfile: "rails_7_2"
19
21
  experimental: [false]
20
22
 
21
- #include:
22
- # - ruby: '2.7'
23
- # gemfile: rails_head
24
- # experimental: true
25
- # - ruby: '3.0'
26
- # gemfile: rails_head
27
- # experimental: true
28
- # - ruby: '3.1'
29
- # gemfile: rails_head
30
- # experimental: true
31
-
32
23
  steps:
33
24
  - uses: actions/checkout@v3
34
25
 
data/Appraisals CHANGED
@@ -6,6 +6,6 @@ appraise "rails-7-0" do
6
6
  gem "rails", "~> 7.0.0"
7
7
  end
8
8
 
9
- appraise "rails-7-1" do
10
- gem "rails", "~> 7.1.0"
11
- end
9
+ appraise "rails-7-2" do
10
+ gem "rails", "~> 7.2.0"
11
+ end
data/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file.
3
3
 
4
4
  This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## 0.19.0
7
+ ### Added
8
+ - Add support for rails 7.2, but leave out rails 7.1 support. This is because ActionView has a breaking bug in 7.1 that renders the template back as a string
9
+ instead of an object, like we need for Pbbuilder https://github.com/rails/rails/pull/51023
10
+
11
+ ## 0.18.0
12
+ ### Added
13
+ - Allow literal assignment of protos to fields
14
+
6
15
  ## 0.17.0
7
16
  ### Changed
8
17
  - Instead of appending to repeated enum message, we're replacing it to avoid issues in case output will be rendered twice
data/Gemfile CHANGED
@@ -7,5 +7,3 @@ gemspec
7
7
 
8
8
  gem "rake"
9
9
  gem "appraisal"
10
-
11
- gem "ruby-lsp"
data/README.md CHANGED
@@ -1,17 +1,19 @@
1
1
  # Pbbuilder
2
- PBBuilder generates [Protobuf](https://developers.google.com/protocol-buffers) Messages with a simple DSL similar to [JBuilder](https://rubygems.org/gems/jbuilder) gem.
2
+ PBBuilder generates [Protobuf](https://developers.google.com/protocol-buffers) Messages with a simple DSL similar to the [JBuilder](https://rubygems.org/gems/jbuilder) gem.
3
3
 
4
4
 
5
- At least Rails 6.1 is required.
5
+ At least Rails 6.1 is required and rails 7.1 is currently not supported.
6
+ There currently is a regression in ActionView (the part of Rails which renders) that forces rendered objects into strings, but for Pbbuilder we need the raw objects.
7
+ This is only present in Rails 7.1, and a fix is released in Rails 7.2. https://github.com/rails/rails/pull/51023
6
8
 
7
9
  ## Compatibility with jBuilder
8
10
  We don't aim to have 100% compitability and coverage with jbuilder gem, but we closely follow jbuilder's API design to maintain familiarity.
9
11
 
10
12
  | | Jbuilder | Pbbuilder |
11
13
  |---|---|---|
12
- | set! | ✅ | ✅ |
13
- | cache! | ✅ | ✅ |
14
- | cache_if! | ✅ | ✅ |
14
+ | set! | ✅ | ✅ |
15
+ | cache! | ✅ | ✅ |
16
+ | cache_if! | ✅ | ✅ |
15
17
  | cache_root! | ✅| |
16
18
  | fragment cache | ✅| ✅ |
17
19
  | extract! | ✅ | ✅ |
@@ -20,7 +22,7 @@ We don't aim to have 100% compitability and coverage with jbuilder gem, but we c
20
22
  | array! | ✅ | |
21
23
  | .call | ✅ | |
22
24
 
23
- Due to protobuf message implementation, there is absolutely no need to implement support for `deep_format_keys!`, `key_format!`, `key_format`, `deep_format_keys`, `ignore_nil!`, `ignore_nil!`, `nil`. So those would never be added.
25
+ Due to the protobuf message implementation, there is absolutely no need to implement support for `deep_format_keys!`, `key_format!`, `key_format`, `deep_format_keys`, `ignore_nil!`, `ignore_nil!`, `nil`. So those would never be added.
24
26
 
25
27
  ## Usage
26
28
  The main difference is that it can use introspection to figure out what kind of protobuf message it needs to create.
@@ -56,7 +58,7 @@ pb.phone_number account.phone_number
56
58
  pb.tag account.tag
57
59
  ```
58
60
 
59
- could be rewritten to a shorter version with a use of `extract!`.
61
+ can be rewritten to a shorter version with the use of `extract!`.
60
62
  ```
61
63
  pb.extract! account, :id, :phone_number, :tag
62
64
  ```
@@ -75,7 +77,7 @@ Using partial while passing a variable to it
75
77
  pb.account partial: "account", account: @account
76
78
  ```
77
79
 
78
- Here is way to use partials with collection while passing a variable to it
80
+ Here is a way to use partials with a collection while passing a variable to it
79
81
 
80
82
  ```
81
83
  pb.accounts @accounts, partial: "account", as: account
@@ -91,7 +93,7 @@ pb.friends partial: "racers/racer", as: :racer, collection: @racers
91
93
  pb.friends "racers/racer", as: :racer, collection: @racers
92
94
  ```
93
95
 
94
- And there are other ways, that don't use Collection Renderer (not very effective probably)
96
+ And there are other ways, that don't use CollectionRenderer
95
97
  ```ruby
96
98
  pb.partial! @racer, racer: Racer.new(123, "Chris Harris", friends)
97
99
  ```
@@ -100,7 +102,7 @@ pb.friends @friends, partial: "racers/racer", as: :racer
100
102
  ```
101
103
 
102
104
  ### Caching
103
- it uses Rails.cache and works like caching in HTML templates:
105
+ It uses Rails.cache and works like caching in HTML templates:
104
106
 
105
107
  ```
106
108
  pb.cache! "cache-key", expires_in: 10.minutes do
@@ -116,7 +118,7 @@ pb.cache_if! !admin?, "cache-key", expires_in: 10.minutes do
116
118
  end
117
119
  ```
118
120
 
119
- Fragment caching currently works through ActionView::CollectionRenderer and can be used only with the following syntax:
121
+ Fragment caching currently works through ActionView::CollectionRenderer and can only be used with the following syntax:
120
122
 
121
123
  ```ruby
122
124
  pb.friends partial: "racers/racer", as: :racer, collection: @racers, cached: true
@@ -146,10 +148,14 @@ $ gem install pbbuilder
146
148
 
147
149
  When debugging, make sure to prepend `::Kernel` to any calls such as `puts` as otherwise the code will think you're trying to add another attribute into protobuf object.
148
150
 
149
- In case, you're looking to use breakpoints for debugging purposes - it's better to use `pry`. Just make sure to [change pbbuilder superclass from `ProxyObject/BasicObject` to `Object`](lib/pbbuilder/pbbuilder.rb).
151
+ In case you're looking to use breakpoints (for debugging purposes via `binding.pry` for instance), let Pbbuilder inherit from `Object` instead of `BasicObject`]
152
+ Seen in:
153
+ [Pbbuilder](lib/pbbuilder/pbbuilder.rb)
154
+ [Errors](lib/pbbuilder/errors.rb)
150
155
 
151
156
  ## Testing
152
- Running `bundle exec appraisal rake test` locally will run entire testsuit with all version of rails. To run tests only for certain rails version do the following `bundle exec appraisal rails-7-0 rake test`
157
+ Running `bundle exec appraisal rake test` locally will run the entire testsuit with all versions of rails.
158
+ To run tests only for a certain rails version do the following `bundle exec appraisal rails-7-0 rake test`
153
159
 
154
160
  To run only one tests from file - use `m` utility. Like this:
155
161
  `bundle exec appraisal rails-7-0 m test/pbbuilder_template_test.rb:182`
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/setup"
4
3
  require "bundler/gem_tasks"
5
4
  require "rake/testtask"
6
5
 
@@ -11,10 +10,28 @@ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["CI"]
11
10
  else
12
11
  Rake::TestTask.new(:test) do |t|
13
12
  t.libs << "test"
14
- t.pattern = "test/**/*_test.rb"
15
- t.verbose = false
16
- t.warning = false
13
+ t.libs << "lib"
14
+
15
+ # Running specific tests with line numbers, like with rails test, is not supported by default in rake.
16
+ # By setting the TESTOPS env var we can however specify the name of a single test with underscores instead of spaces.
17
+ # So run your single test by calling for ex:
18
+ #
19
+ # rake test /Users/sebastian/projects/cheddar/rails-twirp/test/ping_controller_test.rb "uncaught errors should bubble up to the test"
20
+
21
+ file_name = ARGV[1]
22
+ test_name = ARGV[2]&.tr(" ", "_")
23
+
24
+ ENV["TESTOPTS"] = "--verbose"
25
+
26
+ t.test_files = if file_name
27
+ if test_name
28
+ ENV["TESTOPTS"] += " --name=test_#{test_name}"
29
+ end
30
+ [file_name]
31
+ else
32
+ FileList["test/**/*_test.rb"]
33
+ end
17
34
  end
18
35
 
19
36
  task default: :test
20
- end
37
+ end
@@ -4,7 +4,6 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rake"
6
6
  gem "appraisal"
7
- gem "ruby-lsp"
8
7
  gem "rails", "~> 6.1.0"
9
8
 
10
9
  gemspec path: "../"
@@ -4,7 +4,6 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rake"
6
6
  gem "appraisal"
7
- gem "ruby-lsp"
8
7
  gem "rails", "~> 7.0.0"
9
8
 
10
9
  gemspec path: "../"
@@ -4,7 +4,6 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rake"
6
6
  gem "appraisal"
7
- gem "ruby-lsp"
8
- gem "rails", "~> 7.1.0"
7
+ gem "rails", "~> 7.2.0"
9
8
 
10
9
  gemspec path: "../"
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pbbuilder'
4
-
5
- class Pbbuilder
3
+ class Pbbuilder < BasicObject
6
4
  class MergeError < ::StandardError
7
5
  def self.build(current_value, updates)
8
6
  message = "Can't merge #{updates.inspect} into #{current_value.inspect}"
9
7
  self.new(message)
10
8
  end
11
9
  end
12
- end
10
+ end
@@ -17,6 +17,7 @@ class PbbuilderTemplate < Pbbuilder
17
17
  end
18
18
 
19
19
  # Render a partial. Can be called as:
20
+ #
20
21
  # pb.partial! "name/of_partial", argument: 123
21
22
  # pb.partial! "name/of_partial", locals: {argument: 123}
22
23
  # pb.partial! partial: "name/of_partial", argument: 123
@@ -30,7 +31,7 @@ class PbbuilderTemplate < Pbbuilder
30
31
  end
31
32
  end
32
33
 
33
- # Set the value in the message field.
34
+ # Sets the value in the message field.
34
35
  #
35
36
  # @example
36
37
  # pb.friends @friends, partial: "friend", as: :friend
@@ -39,31 +40,30 @@ class PbbuilderTemplate < Pbbuilder
39
40
  # pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
40
41
 
41
42
  def set!(field, *args, **kwargs, &block)
42
- # If partial options are being passed, we render a submessage with a partial
43
+ # If any partial options are being passed, we render a submessage with a partial
43
44
  if kwargs.has_key?(:partial)
44
45
  if args.one? && kwargs.has_key?(:as)
45
- # example syntax that should end up here:
46
+ # Example syntax that should end up here:
46
47
  # pb.friends @friends, partial: "friend", as: :friend
47
48
  # Call set! on the super class, passing in a block that renders a partial for every element
48
49
  super(field, *args) do |element|
49
50
  _set_inline_partial(element, kwargs)
50
51
  end
51
52
  elsif kwargs.has_key?(:collection) && kwargs.has_key?(:as)
52
- # example syntax that should end up here:
53
+ # Example syntax that should end up here:
53
54
  # pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
54
55
 
55
56
  _render_collection_with_options(field, kwargs[:collection], kwargs)
56
57
  else
57
- # # example syntax that should end up here:
58
+ # Example syntax that should end up here:
58
59
  # pb.best_friend partial: "person", person: @best_friend
59
-
60
60
  super(field, *args) do
61
61
  _render_partial_with_options(kwargs)
62
62
  end
63
63
  end
64
64
  else
65
65
  if args.one? && kwargs.has_key?(:collection) && kwargs.has_key?(:as)
66
- # example syntax that should end up here:
66
+ # Example syntax that should end up here:
67
67
  # pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
68
68
  _render_collection_with_options(field, kwargs[:collection], kwargs.merge(partial: args.first))
69
69
  else
@@ -72,7 +72,7 @@ class PbbuilderTemplate < Pbbuilder
72
72
  end
73
73
  end
74
74
 
75
- # Caches fragment of message. Can be called like the following:
75
+ # Caches a fragment of a message with a given cache key. Can be called like the following:
76
76
  # 'pb.cache! "cache-key" do; end'
77
77
  # 'pb.cache! "cache-key", expire_in: 1.min do; end'
78
78
  #
@@ -107,16 +107,16 @@ class PbbuilderTemplate < Pbbuilder
107
107
 
108
108
  private
109
109
 
110
- # Uses ActionView::CollectionRenderer to render collection effectively and to use rails built in fragment caching support.
110
+ # Uses ActionView::CollectionRenderer to render the collection effectively and to use rails' built-in fragment caching support.
111
111
  #
112
- # The way recursive rendering works is that the CollectionRenderer needs to be aware of the node its currently rendering and parent node.
113
- # There is no need to know the entire "stack" of nodes. ActionView::CollectionRenderer would traverse to bottom node render it first and then go one leve up in stack,
114
- # rince and repeat until entire stack is rendered.
112
+ # The way recursive rendering works is that the CollectionRenderer needs to be aware of the node it's currently rendering and it's parent node.
113
+ # There is no need to know the entire "stack" of nodes. ActionView::CollectionRenderer will traverse to the bottom node, render it first and then go one level up in the stack.
114
+ # Rinse and repeat until the entire stack is rendered.
115
115
 
116
116
  # CollectionRenderer uses locals[:pb] to render the partial as a protobuf message,
117
117
  # but also needs locals[:pb_parent] to apply the rendered partial to the top level protobuf message.
118
118
 
119
- # This logic can be found in CollectionRenderer#build_rendered_collection method that we overwrote.
119
+ # This logic can be found in the CollectionRenderer#build_rendered_collection method that we overwrote.
120
120
  def _render_collection_with_options(field, collection, options)
121
121
  partial = options[:partial]
122
122
 
@@ -128,15 +128,15 @@ class PbbuilderTemplate < Pbbuilder
128
128
  options[:locals].merge!(field: field)
129
129
 
130
130
  if options.has_key?(:layout)
131
- raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
131
+ ::Kernel.raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
132
132
  end
133
133
 
134
134
  if options.has_key?(:spacer_template)
135
- raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
135
+ ::Kernel.raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
136
136
  end
137
137
 
138
138
  CollectionRenderer
139
- .new(@context.lookup_context, options) { |&block| _scope(message[field.to_s],&block) }
139
+ .new(@context.lookup_context, options) { |&block| _scope(message[field.to_s], &block) }
140
140
  .render_collection_with_partial(collection, partial, @context, nil)
141
141
  end
142
142
 
@@ -172,10 +172,9 @@ class PbbuilderTemplate < Pbbuilder
172
172
  begin
173
173
  ::Rails.cache.write(key, value, options)
174
174
  rescue ::SystemCallError
175
- # In case ActiveSupport::Cache::FileStore in Rails is used as a cache,
176
- # File.atomic_write can have a race condition and fail to rename temporary
177
- # file. We're attempting to recover from that, by catching this specific
178
- # error and returning a value.
175
+ # In case `ActiveSupport::Cache::FileStore` in Rails is used as a cache,
176
+ # `File.atomic_write` can have a race condition and fails to rename temporary file.
177
+ # We're attempting to recover from that by catching this specific error and returning a value.
179
178
  #
180
179
  # @see https://github.com/rails/rails/pull/44151
181
180
  # @see https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb#L50
data/lib/pbbuilder.rb CHANGED
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pbbuilder/pbbuilder"
4
- require 'pbbuilder/errors'
3
+ require "pbbuilder/errors"
5
4
  require "pbbuilder/protobuf_extension"
6
5
  require "pbbuilder/railtie" if defined?(Rails)
7
6
 
8
-
9
- # Pbbuilder makes it easy to create a protobuf message using the builder pattern
7
+ # Pbbuilder makes it easy to create a protobuf message using the builder pattern.
10
8
  # It is heavily inspired by jbuilder
11
9
  #
12
10
  # Given this example message definition:
@@ -15,8 +13,10 @@ require "pbbuilder/railtie" if defined?(Rails)
15
13
  # repeated Person friends = 2;
16
14
  # }
17
15
  #
18
- # You could use Pbbuilder as follows:
16
+ # You can use Pbbuilder as follows:
17
+ #
19
18
  # person = RPC::Person.new
19
+ #
20
20
  # Pbbuilder.new(person) do |pb|
21
21
  # pb.name "Hello"
22
22
  # pb.friends [1, 2, 3] do |number|
@@ -29,7 +29,7 @@ require "pbbuilder/railtie" if defined?(Rails)
29
29
  # It basically works exactly like jbuilder. The main difference is that it can use introspection to figure out what kind
30
30
  # of protobuf message it needs to create.
31
31
 
32
- class Pbbuilder
32
+ class Pbbuilder < BasicObject
33
33
  def initialize(message)
34
34
  @message = message
35
35
 
@@ -48,11 +48,15 @@ class Pbbuilder
48
48
  !!_descriptor_for_field(field)
49
49
  end
50
50
 
51
+ # When calling for ex: response.drivers, where response is a Google::Protobuf object, 'drivers' is not a method on that. These
52
+ # methods (or messages in our case) get added here. This is of course based on what kind of message it is. Singular, an array
53
+ # (repeated) etc. with their arguments.
51
54
  def set!(field, *args, &block)
52
55
  name = field.to_s
53
56
  descriptor = _descriptor_for_field(name)
54
- ::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?
57
+ ::Kernel.raise ::ArgumentError, "Unknown field: #{name}" if descriptor.nil?
55
58
 
59
+ # An block is used to pass on it's children
56
60
  if ::Kernel.block_given?
57
61
  ::Kernel.raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
58
62
 
@@ -66,9 +70,11 @@ class Pbbuilder
66
70
  # example syntax that should end up here:
67
71
  # pb.field { pb.name "hello" }
68
72
  ::Kernel.raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty?
73
+
69
74
  message = (@message[name] ||= _new_message_from_descriptor(descriptor))
70
75
  _scope(message, &block)
71
76
  end
77
+ # No block given, but with 1 argument
72
78
  elsif args.length == 1
73
79
  arg = args.first
74
80
  if descriptor.label == :repeated
@@ -82,20 +88,31 @@ class Pbbuilder
82
88
 
83
89
  @message[name].replace arg.to_ary
84
90
  elsif arg.respond_to?(:to_ary) && descriptor.type.eql?(:message)
85
- # example syntax that should end up here:
86
- # pb.friends [Person.new(name: "Johnny Test"), Person.new(name: "Max Verstappen")]
87
-
88
- args.flatten.each {|obj| @message[name].push descriptor.subtype.msgclass.new(obj)}
91
+ # pb.friends [Person.new(name: "Johnny Test"), Person.new(name: "Max Verstappen")]
92
+ #
93
+ # Accepts both objects that can be to_hash-translated into keyword arguments
94
+ # for creating a nested proto message object and actual proto message objects
95
+ # which can be assigned "as is". With "as-is" assignment the proto message can be stored
96
+ # in memory and reused
97
+ nested_messages = arg.map do |arg_member_message_or_hash|
98
+ # If the arg passed already is a proto message and is the same as the
99
+ # field expects - just use it as-is
100
+ if arg_member_message_or_hash.is_a?(descriptor.subtype.msgclass)
101
+ arg_member_message_or_hash
102
+ else
103
+ descriptor.subtype.msgclass.new(arg_member_message_or_hash)
104
+ end
105
+ end
106
+ @message[name].replace(nested_messages)
89
107
  else
90
108
  # example syntax that should end up here:
91
109
  # pb.fields "one"
92
-
93
110
  @message[name].replace [arg]
94
111
  end
95
112
  else
96
113
  # example syntax that should end up here:
97
114
  # pb.field "value"
98
-
115
+
99
116
  @message[name] = arg
100
117
  end
101
118
  else
@@ -117,6 +134,8 @@ class Pbbuilder
117
134
  end
118
135
  end
119
136
 
137
+ # Shorthand command for getting a few attributes from an object.
138
+ # pb.extract! racer, :name, :id, :age
120
139
  def extract!(element, *args)
121
140
  args.each { |arg| @message[arg.to_s] = element.send(arg) }
122
141
  end
@@ -170,6 +189,7 @@ class Pbbuilder
170
189
  @message
171
190
  end
172
191
 
192
+ # @param field string
173
193
  def new_message_for(field)
174
194
  descriptor = _descriptor_for_field(field)
175
195
  ::Kernel.raise ::ArgumentError, "Unknown field #{field}" if descriptor.nil?
@@ -179,11 +199,14 @@ class Pbbuilder
179
199
 
180
200
  private
181
201
 
202
+ # Lookup the field name (or 'attribute' name, for ex "best_friend") on the Google descriptor (Google::Protobuf::Descriptor) of our
203
+ # message object.
204
+ # @param field string
182
205
  def _descriptor_for_field(field)
183
206
  @message.class.descriptor.lookup(field.to_s)
184
207
  end
185
208
 
186
- # Appends protobuf message with existing @message object
209
+ # Appends protobuf objects to our 'repeated' attribute. This can create a list of items to a repeated field.
187
210
  #
188
211
  # @param name string
189
212
  # @param descriptor Google::Protobuf::FieldDescriptor
@@ -199,7 +222,8 @@ class Pbbuilder
199
222
  @message[name].push(*elements)
200
223
  end
201
224
 
202
- # Yields an Protobuf object in a scope of message and provided values.
225
+ # Yields a Protobuf object in the scope of message and provided values.
226
+ # This will 'assign' the field values as it were to the message attributes.
203
227
  #
204
228
  # @param message Google::Protobuf::(field_type)
205
229
  def _scope(message)
@@ -211,7 +235,7 @@ class Pbbuilder
211
235
  @message = old_message
212
236
  end
213
237
 
214
- # Build up empty protobuf message based on descriptor
238
+ # Builds up an empty protobuf message based on the given descriptor.
215
239
  #
216
240
  # @param descriptor Google::Protobuf::FieldDescriptor
217
241
  def _new_message_from_descriptor(descriptor)
@@ -222,4 +246,4 @@ class Pbbuilder
222
246
  message_class = message_descriptor.msgclass
223
247
  message_class.new
224
248
  end
225
- end
249
+ end
data/pbbuilder.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "pbbuilder"
5
- spec.version = "0.17.0"
5
+ spec.version = "0.19.0"
6
6
  spec.authors = ["Bouke van der Bijl"]
7
7
  spec.email = ["bouke@cheddar.me"]
8
8
  spec.homepage = "https://github.com/cheddar-me/pbbuilder"
@@ -57,7 +57,7 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
57
57
  test "collection partial with fragment caching enabled" do
58
58
  template = <<-PBBUILDER
59
59
  racers = [Racer.new(1, "Johnny Test", [], nil, API::Asset.new(url: "https://google.com/test1.svg")), Racer.new(2, "Max Verstappen", [])]
60
- pb.friends partial: "racers/racer", as: :racer, collection: racers, cached: true
60
+ pb.friends partial: "racers/racer", collection: racers, cached: true, as: :racer
61
61
  PBBUILDER
62
62
  result = render(template)
63
63
 
@@ -83,7 +83,7 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
83
83
  end
84
84
 
85
85
  test "render collections with partial as arg" do
86
- skip("This will be addressed in future version of a gem")
86
+ skip("This will be addressed in a future version of this gem")
87
87
  result = render('pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')
88
88
 
89
89
  assert_equal 2, result.friends.count
@@ -30,6 +30,23 @@ class PbbuilderTest < ActiveSupport::TestCase
30
30
  assert_equal "Eggs", person.favourite_foods["Breakfast"]
31
31
  end
32
32
 
33
+ test "allows assignment of prefab nested proto messages" do
34
+ max_proto = API::Person.new(name: "Max Verstappen")
35
+ james_proto = API::Person.new(name: "James Hunt")
36
+ friend_protos = [max_proto, james_proto]
37
+
38
+ person = Pbbuilder.new(API::Person.new) do |pb|
39
+ pb.name "Niki Lauda"
40
+ pb.friends friend_protos
41
+ pb.best_friend james_proto
42
+ end.target!
43
+
44
+ assert_equal "Niki Lauda", person.name
45
+ assert_equal "Max Verstappen", person.friends[0].name
46
+ assert_equal "James Hunt", person.friends[1].name
47
+ assert_equal "James Hunt", person.best_friend.name
48
+ end
49
+
33
50
  test "replaces the repeated field's value with the last one set" do
34
51
  person = Pbbuilder.new(API::Person.new) do |pb|
35
52
  pb.field_mask do
@@ -45,7 +62,7 @@ class PbbuilderTest < ActiveSupport::TestCase
45
62
 
46
63
  assert_equal(["ok", "that's"], p.field_mask.paths)
47
64
  end
48
-
65
+
49
66
  test "sets the last value of the repeated field to be the only value" do
50
67
  person = Pbbuilder.new(API::Person.new) do |pb|
51
68
  pb.field_mask do
data/test/test_helper.rb CHANGED
@@ -55,10 +55,16 @@ class << Rails
55
55
  end
56
56
  end
57
57
 
58
+ Pbbuilder::CollectionRenderer.collection_cache = Rails.cache
59
+
58
60
  class Racer < Struct.new(:id, :name, :friends, :best_friend, :logo)
59
61
  extend ActiveModel::Naming
60
62
  include ActiveModel::Conversion
61
63
 
64
+ def cache_key
65
+ "racer-#{id}"
66
+ end
67
+
62
68
  # Fragment caching needs to know, if record could be persisted. We set it to false, this is a default in ActiveModel::API.
63
69
  def persisted?
64
70
  false
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.17.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bouke van der Bijl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-22 00:00:00.000000000 Z
11
+ date: 2024-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -89,13 +89,12 @@ files:
89
89
  - gemfiles/rails_6_1.gemfile
90
90
  - gemfiles/rails_7.gemfile
91
91
  - gemfiles/rails_7_0.gemfile
92
- - gemfiles/rails_7_1.gemfile
92
+ - gemfiles/rails_7_2.gemfile
93
93
  - gemfiles/rails_head.gemfile
94
94
  - lib/pbbuilder.rb
95
95
  - lib/pbbuilder/collection_renderer.rb
96
96
  - lib/pbbuilder/errors.rb
97
97
  - lib/pbbuilder/handler.rb
98
- - lib/pbbuilder/pbbuilder.rb
99
98
  - lib/pbbuilder/protobuf_extension.rb
100
99
  - lib/pbbuilder/railtie.rb
101
100
  - lib/pbbuilder/template.rb
@@ -123,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
122
  - !ruby/object:Gem::Version
124
123
  version: '0'
125
124
  requirements: []
126
- rubygems_version: 3.3.7
125
+ rubygems_version: 3.5.18
127
126
  signing_key:
128
127
  specification_version: 4
129
128
  summary: Generate Protobuf Messages with a simple DSL similar to JBuilder
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Pbbuilder = Class.new(begin
4
- require 'active_support/proxy_object'
5
- ActiveSupport::ProxyObject
6
- rescue LoadError
7
- require 'active_support/basic_object'
8
- ActiveSupport::BasicObject
9
- end)