pbbuilder 0.18.0 → 0.20.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: 75cb2682f8933e0e8acb2b3bb6a525df5447dab8a65b2d8509f54a3c2b430c35
4
- data.tar.gz: '0282cfeb404460224fd8cb2d32d134335b9d307d2edefc4af854368b9c8788e1'
3
+ metadata.gz: 9f241a460f2ab7e8383c37db199821f8cb5e8d2f69c262f38c7c462a4d7827e0
4
+ data.tar.gz: 3e8c43a8d0c0362049a9cb8a709c86eac64d86e12366812f2b996b1e81d73c23
5
5
  SHA512:
6
- metadata.gz: d429891ca19333b9e626ccd9bf63462fbe61eea672c8ac14f427aafdc392a2f12e36370e63f1a4522c384b4375e81433d63d804f447d508346744aedca448fbf
7
- data.tar.gz: 1af6634160780c84f93a99d9b6b282e90500da24d654bfa36f1220b48260bec074412da69f83267a2df8f935e33218b332a27cf102765e6eff7b3db853ec686e
6
+ metadata.gz: '03691d015507cb98d862c4f1c352a08c20e0968f0fae3a23795bec6e6ffdbef6b3e54dca9057364c32e353bfc561e8f117cf02219ad2c73725ae004b7fd1590b'
7
+ data.tar.gz: b52b5324090887effbaba0ae6ff79ebe9cc7c08ffae945037883d42375bca4ec53bd5a37dc4a9d144c488a6ba5e7ffce88d05f3e9f7ba1a260af5f0ed55107ed
@@ -13,24 +13,12 @@ jobs:
13
13
  strategy:
14
14
  fail-fast: false
15
15
  matrix:
16
- ruby: ["3.0", "3.1", "3.2", "3.3"]
17
-
18
- gemfile: [ "rails_6_1", "rails_7_0"]
16
+ ruby: ["3.2.2"]
17
+ gemfile: ["rails_7_2", "rails_8_0"]
19
18
  experimental: [false]
20
19
 
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
20
  steps:
33
- - uses: actions/checkout@v3
21
+ - uses: actions/checkout@v4
34
22
 
35
23
  - uses: ruby/setup-ruby@v1
36
24
  with:
data/.gitignore CHANGED
@@ -18,5 +18,5 @@ gemfiles/*.lock
18
18
  Gemfile.lock
19
19
  .ruby-version
20
20
  pkg
21
-
21
+ .vscode/
22
22
 
data/Appraisals CHANGED
@@ -1,11 +1,9 @@
1
- appraise "rails-6-1" do
2
- gem "rails", "~> 6.1.0"
1
+ appraise "rails-7-2" do
2
+ gem "rails", "~> 7.2.0"
3
+ gem "google-protobuf", "~> 3.25", "< 4.0"
3
4
  end
4
5
 
5
- appraise "rails-7-0" do
6
- gem "rails", "~> 7.0.0"
7
- end
8
-
9
- appraise "rails-7-1" do
10
- gem "rails", "~> 7.1.0"
6
+ appraise "rails-8-0" do
7
+ gem "rails", "~> 8.0.2"
8
+ gem "google-protobuf", "~> 4.0", "< 5.0"
11
9
  end
data/CHANGELOG.md CHANGED
@@ -1,60 +1,55 @@
1
- # Pbbuilder Changelog
2
- All notable changes to this project will be documented in this file.
1
+ ## Unreleased
3
2
 
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).
3
+ - Remove official support for Rails 7.0 and Rails 6.x, add Appraisal config for Rails 8.x
4
+ - Bump actions/checkout from 3 to 4 (#40)
5
+ - Drop support for rails v6.* versions (#60)
6
+ - Regenerate Appraisals, drop Rails 7.0
7
+ - Allow usage of google-protobuf 4.x
8
+ - Use an actual .proto for testing
9
+ - Test with both google-protobuf 3.25 and 4.x
10
+ - Remove --verbose when running rake test
11
+
12
+ ## 0.19.0
13
+ - 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 instead of an object, like we need for Pbbuilder https://github.com/rails/rails/pull/51023 This also removes the uses of the Rails variant of BasicObject in favor of the Ruby built-in.
14
+
15
+ ## 0.18.0
16
+ - Allow literal assignment of protos to fields
5
17
 
6
18
  ## 0.17.0
7
- ### Changed
8
19
  - Instead of appending to repeated enum message, we're replacing it to avoid issues in case output will be rendered twice
9
20
  - If one field was defined twice, only last definition will end up in output
10
-
11
- ## Fixed
12
21
  - Fixed CI by locking 3 version or lower of google-protobuf dependency.
13
22
 
14
23
  ## 0.16.2
15
- ### Added
16
24
  - Add support for partial as a first argument , e.g.`pb.friends "racers/racer", as: :racer, collection: @racers`
17
25
  - Add tests to verify that fragment caching is operational
18
26
 
19
27
  ## 0.16.1
20
- ### Changed
21
28
  - Deal properly with recursive protobuf messages while using ActiveView::CollectionRenderer
22
29
 
23
30
  ## 0.16.0
24
- ### Added
25
31
  - Added support for new collection rendering, that is backed by ActiveView::CollectionRenderer.
26
-
27
- ### Changed
28
32
  - Refactoring and simplification of #merge! method without a change in functionality.
29
33
 
30
34
  ## 0.15.1
31
- ### Fixed
32
35
  - #merge! method to handle repeated unintialized message object
33
36
 
34
37
  ## 0.15.0
35
- ### Changed
36
38
  - #merge! method was refactored to accomodate caching for all data types (especially those that are :repeated)
37
39
 
38
40
  ## 0.14.0
39
- ### Added
40
41
  - Adding `frozen_string_literal: true` to all files.
41
42
 
42
- ## 0.13.2 2023.02.3
43
- ### Fixed
43
+ ## 0.13.2
44
44
  - In case ActiveSupport::Cache::FileStore in Rails is used as a cache, File.atomic_write can have a race condition and fail to rename temporary file. We're attempting to recover from that, by catching this specific error and returning a value.
45
45
 
46
- ## 0.13.1 2023.01.24
47
- ### Added
46
+ ## 0.13.1
48
47
  - #merge! to support boolean values
49
48
 
50
- ## 0.13.0 2023.01.18
51
- ### Added
49
+ ## 0.13.0
52
50
  - #merge! method added for PbbuilderTemplate class
53
51
  - ActiveSupport added as a dependency for gem
54
52
  - Fragment Caching support added, with #cache! and #cache_if! methods in PbbuilderTemplate class.
55
-
56
-
57
- ### Changed
58
53
  - Appraisal is properly configured to run against all rubies and rails combinations.
59
54
  - Supported ruby version's are 2.7, 3.0, 3.1
60
55
  - Superclass for pbbuilder is now active_support/proxy_object, with a fallback to active_support/basic_object.
@@ -62,10 +57,7 @@ This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
62
57
  - `rails` from version 6.1.4.4 to 6.1.7, and from version 7.0.1 to 7.0.4
63
58
  - `google-protobuf` is let loose
64
59
  - `bundler` from version 2.3.4 to 2.3.22
65
-
66
- ### Removed
67
- - TestUnit dependency
68
-
60
+ - TestUnit dependency removed
69
61
 
70
62
  ## 0.12.0 Prior to 2022-10-14
71
63
 
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,22 +1,22 @@
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
+ ## Requirements
5
+ This gem only supports Rails 7.0 and Rails 7.2, **7.1 is not supported**.
4
6
 
5
- At least Rails 6.1 is required.
7
+ 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.
8
+ 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
9
 
7
- > [!WARNING]
8
- > There currently is a regression in ActionView (the part of Rails which renders) that forces rendered objects into strings. This is only present
9
- > in Rails 7.1, and is fixed in Rails. However, a 7.1 gem containing the fix hasn't been released yet. For the moment you should refrain
10
- > from using pbbuilder and rails-twirp with Rails 7.1 and wait for the next version to be released.
10
+ It might work on rails v6 (it worked previously), but we don't guarantee that and don't test against these versions anymore.
11
11
 
12
12
  ## Compatibility with jBuilder
13
13
  We don't aim to have 100% compitability and coverage with jbuilder gem, but we closely follow jbuilder's API design to maintain familiarity.
14
14
 
15
15
  | | Jbuilder | Pbbuilder |
16
16
  |---|---|---|
17
- | set! | ✅ | ✅ |
18
- | cache! | ✅ | ✅ |
19
- | cache_if! | ✅ | ✅ |
17
+ | set! | ✅ | ✅ |
18
+ | cache! | ✅ | ✅ |
19
+ | cache_if! | ✅ | ✅ |
20
20
  | cache_root! | ✅| |
21
21
  | fragment cache | ✅| ✅ |
22
22
  | extract! | ✅ | ✅ |
@@ -25,7 +25,7 @@ We don't aim to have 100% compitability and coverage with jbuilder gem, but we c
25
25
  | array! | ✅ | |
26
26
  | .call | ✅ | |
27
27
 
28
- 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.
28
+ 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.
29
29
 
30
30
  ## Usage
31
31
  The main difference is that it can use introspection to figure out what kind of protobuf message it needs to create.
@@ -61,7 +61,7 @@ pb.phone_number account.phone_number
61
61
  pb.tag account.tag
62
62
  ```
63
63
 
64
- could be rewritten to a shorter version with a use of `extract!`.
64
+ can be rewritten to a shorter version with the use of `extract!`.
65
65
  ```
66
66
  pb.extract! account, :id, :phone_number, :tag
67
67
  ```
@@ -80,7 +80,7 @@ Using partial while passing a variable to it
80
80
  pb.account partial: "account", account: @account
81
81
  ```
82
82
 
83
- Here is way to use partials with collection while passing a variable to it
83
+ Here is a way to use partials with a collection while passing a variable to it
84
84
 
85
85
  ```
86
86
  pb.accounts @accounts, partial: "account", as: account
@@ -96,7 +96,7 @@ pb.friends partial: "racers/racer", as: :racer, collection: @racers
96
96
  pb.friends "racers/racer", as: :racer, collection: @racers
97
97
  ```
98
98
 
99
- And there are other ways, that don't use Collection Renderer (not very effective probably)
99
+ And there are other ways, that don't use CollectionRenderer
100
100
  ```ruby
101
101
  pb.partial! @racer, racer: Racer.new(123, "Chris Harris", friends)
102
102
  ```
@@ -105,7 +105,7 @@ pb.friends @friends, partial: "racers/racer", as: :racer
105
105
  ```
106
106
 
107
107
  ### Caching
108
- it uses Rails.cache and works like caching in HTML templates:
108
+ It uses Rails.cache and works like caching in HTML templates:
109
109
 
110
110
  ```
111
111
  pb.cache! "cache-key", expires_in: 10.minutes do
@@ -121,7 +121,7 @@ pb.cache_if! !admin?, "cache-key", expires_in: 10.minutes do
121
121
  end
122
122
  ```
123
123
 
124
- Fragment caching currently works through ActionView::CollectionRenderer and can be used only with the following syntax:
124
+ Fragment caching currently works through ActionView::CollectionRenderer and can only be used with the following syntax:
125
125
 
126
126
  ```ruby
127
127
  pb.friends partial: "racers/racer", as: :racer, collection: @racers, cached: true
@@ -151,16 +151,32 @@ $ gem install pbbuilder
151
151
 
152
152
  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.
153
153
 
154
- 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).
154
+ In case you're looking to use breakpoints (for debugging purposes via `binding.pry` for instance), let Pbbuilder inherit from `Object` instead of `BasicObject`]
155
+ Seen in:
156
+ [Pbbuilder](lib/pbbuilder/pbbuilder.rb)
157
+ [Errors](lib/pbbuilder/errors.rb)
155
158
 
156
159
  ## Testing
157
- 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`
158
160
 
159
- To run only one tests from file - use `m` utility. Like this:
160
- `bundle exec appraisal rails-7-0 m test/pbbuilder_template_test.rb:182`
161
+ `pbbuilder` is set up with Appraisal so that we can test a couple Rails and google-protobuf versions against our changes. To run the tests for all setups:
162
+
163
+ ```bash
164
+ $ bundle exec appraisal install
165
+ $ bundle exec appraisal rake test
166
+
167
+ To run tests only for a certain pre-set Gemfile (check the `Appraisals` file to see what's defined):
168
+
169
+ ```bash
170
+ $ bundle exec appraisal rails-7-2 rake test`
171
+ ```
172
+
173
+ To run only one test use `m` utility. Like this:
174
+ `bundle exec appraisal rails-7-2 m test/pbbuilder_template_test.rb:182`
161
175
 
162
176
  ## Contributing
177
+
163
178
  Everyone is welcome to contribute.
164
179
 
165
180
  ## License
181
+
166
182
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
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"] = ""
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,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rake"
6
6
  gem "appraisal"
7
- gem "ruby-lsp"
8
- gem "rails", "~> 6.1.0"
7
+ gem "rails", "~> 7.2.0"
8
+ gem "google-protobuf", "~> 3.25", "< 4.0"
9
9
 
10
10
  gemspec path: "../"
@@ -4,6 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rake"
6
6
  gem "appraisal"
7
- gem "rails", "~> 6.0"
7
+ gem "rails", "~> 8.0.2"
8
+ gem "google-protobuf", "~> 4.0", "< 5.0"
8
9
 
9
10
  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
@@ -106,7 +112,7 @@ class Pbbuilder
106
112
  else
107
113
  # example syntax that should end up here:
108
114
  # pb.field "value"
109
-
115
+
110
116
  @message[name] = arg
111
117
  end
112
118
  else
@@ -128,6 +134,8 @@ class Pbbuilder
128
134
  end
129
135
  end
130
136
 
137
+ # Shorthand command for getting a few attributes from an object.
138
+ # pb.extract! racer, :name, :id, :age
131
139
  def extract!(element, *args)
132
140
  args.each { |arg| @message[arg.to_s] = element.send(arg) }
133
141
  end
@@ -181,6 +189,7 @@ class Pbbuilder
181
189
  @message
182
190
  end
183
191
 
192
+ # @param field string
184
193
  def new_message_for(field)
185
194
  descriptor = _descriptor_for_field(field)
186
195
  ::Kernel.raise ::ArgumentError, "Unknown field #{field}" if descriptor.nil?
@@ -190,11 +199,14 @@ class Pbbuilder
190
199
 
191
200
  private
192
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
193
205
  def _descriptor_for_field(field)
194
206
  @message.class.descriptor.lookup(field.to_s)
195
207
  end
196
208
 
197
- # 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.
198
210
  #
199
211
  # @param name string
200
212
  # @param descriptor Google::Protobuf::FieldDescriptor
@@ -210,7 +222,8 @@ class Pbbuilder
210
222
  @message[name].push(*elements)
211
223
  end
212
224
 
213
- # 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.
214
227
  #
215
228
  # @param message Google::Protobuf::(field_type)
216
229
  def _scope(message)
@@ -222,7 +235,7 @@ class Pbbuilder
222
235
  @message = old_message
223
236
  end
224
237
 
225
- # Build up empty protobuf message based on descriptor
238
+ # Builds up an empty protobuf message based on the given descriptor.
226
239
  #
227
240
  # @param descriptor Google::Protobuf::FieldDescriptor
228
241
  def _new_message_from_descriptor(descriptor)
data/pbbuilder.gemspec CHANGED
@@ -2,20 +2,21 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "pbbuilder"
5
- spec.version = "0.18.0"
6
- spec.authors = ["Bouke van der Bijl"]
7
- spec.email = ["bouke@cheddar.me"]
5
+ spec.version = "0.20.0"
6
+ spec.authors = ["Bouke van der Bijl", "Julik Tarkhanov", "Stas Katkov", "Sebastian van Hesteren"]
7
+ spec.email = ["julik@cheddar.me"]
8
8
  spec.homepage = "https://github.com/cheddar-me/pbbuilder"
9
- spec.summary = "Generate Protobuf Messages with a simple DSL similar to JBuilder"
9
+ spec.summary = "Generate Protobuf messages with a simple DSL similar to JBuilder"
10
10
  spec.license = "MIT"
11
11
 
12
- spec.required_ruby_version = '>= 2.7'
12
+ spec.required_ruby_version = '>= 3.2'
13
13
 
14
14
  spec.files = `git ls-files`.split("\n")
15
15
  spec.test_files = `git ls-files -- test/*`.split("\n")
16
16
 
17
- spec.add_dependency "google-protobuf", "~> 3.25"
17
+ spec.add_dependency "google-protobuf", ">= 3.25", "< 5.0"
18
18
  spec.add_dependency "activesupport"
19
- spec.add_development_dependency 'm'
19
+ spec.add_development_dependency "m"
20
20
  spec.add_development_dependency "pry"
21
+ spec.add_development_dependency "rake"
21
22
  end
@@ -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
@@ -62,7 +62,7 @@ class PbbuilderTest < ActiveSupport::TestCase
62
62
 
63
63
  assert_equal(["ok", "that's"], p.field_mask.paths)
64
64
  end
65
-
65
+
66
66
  test "sets the last value of the repeated field to be the only value" do
67
67
  person = Pbbuilder.new(API::Person.new) do |pb|
68
68
  pb.field_mask do
data/test/test_helper.rb CHANGED
@@ -21,28 +21,14 @@ require "pry"
21
21
 
22
22
  ActiveSupport.test_order = :random
23
23
 
24
- Google::Protobuf::DescriptorPool.generated_pool.build do
25
- add_file("pbbuilder.proto", syntax: :proto3) do
26
- add_message "pbbuildertest.Person" do
27
- optional :name, :string, 1
28
- repeated :friends, :message, 2, "pbbuildertest.Person"
29
- optional :best_friend, :message, 3, "pbbuildertest.Person"
30
- repeated :nicknames, :string, 4
31
- optional :field_mask, :message, 5, "google.protobuf.FieldMask"
32
- map :favourite_foods, :string, :string, 6
33
- repeated :tags, :string, 7
34
- optional :last_name, :string, 8
35
- optional :boolean_me, :bool, 9
36
- optional :logo, :message, 10, "pbbuildertest.Asset"
37
- end
38
-
39
- add_message "pbbuildertest.Asset" do
40
- optional :url, :string, 1
41
- optional :url_2x, :string, 2
42
- optional :url_3x, :string, 3
43
- end
44
- end
24
+ # Regenerate Ruby descriptors from proto if needed, and require them.
25
+ # This does require protoc to be installed
26
+ proto_file = File.expand_path("test_proto.proto", __dir__)
27
+ ruby_out = File.expand_path("test_proto_pb.rb", __dir__)
28
+ if !File.exist?(ruby_out) || File.mtime(ruby_out) < File.mtime(proto_file)
29
+ system("protoc --proto_path=#{File.dirname(proto_file)} --ruby_out=#{File.dirname(ruby_out)} #{proto_file}")
45
30
  end
31
+ require_relative "test_proto_pb"
46
32
 
47
33
  module API
48
34
  Person = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pbbuildertest.Person").msgclass
@@ -55,10 +41,16 @@ class << Rails
55
41
  end
56
42
  end
57
43
 
44
+ Pbbuilder::CollectionRenderer.collection_cache = Rails.cache
45
+
58
46
  class Racer < Struct.new(:id, :name, :friends, :best_friend, :logo)
59
47
  extend ActiveModel::Naming
60
48
  include ActiveModel::Conversion
61
49
 
50
+ def cache_key
51
+ "racer-#{id}"
52
+ end
53
+
62
54
  # Fragment caching needs to know, if record could be persisted. We set it to false, this is a default in ActiveModel::API.
63
55
  def persisted?
64
56
  false
@@ -0,0 +1,27 @@
1
+ // This is the source proto file from which Ruby proto descriptors are generated.
2
+ // See test/test_helper.rb for the generation process.
3
+
4
+ syntax = "proto3";
5
+
6
+ package pbbuildertest;
7
+
8
+ import "google/protobuf/field_mask.proto";
9
+
10
+ message Person {
11
+ string name = 1;
12
+ repeated Person friends = 2;
13
+ Person best_friend = 3;
14
+ repeated string nicknames = 4;
15
+ google.protobuf.FieldMask field_mask = 5;
16
+ map<string, string> favourite_foods = 6;
17
+ repeated string tags = 7;
18
+ string last_name = 8;
19
+ bool boolean_me = 9;
20
+ Asset logo = 10;
21
+ }
22
+
23
+ message Asset {
24
+ string url = 1;
25
+ string url_2x = 2;
26
+ string url_3x = 3;
27
+ }
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: test_proto.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+ require 'google/protobuf/field_mask_pb'
8
+
9
+
10
+ descriptor_data = "\n\x10test_proto.proto\x12\rpbbuildertest\x1a google/protobuf/field_mask.proto\"\x81\x03\n\x06Person\x12\x0c\n\x04name\x18\x01 \x01(\t\x12&\n\x07\x66riends\x18\x02 \x03(\x0b\x32\x15.pbbuildertest.Person\x12*\n\x0b\x62\x65st_friend\x18\x03 \x01(\x0b\x32\x15.pbbuildertest.Person\x12\x11\n\tnicknames\x18\x04 \x03(\t\x12.\n\nfield_mask\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\x12\x42\n\x0f\x66\x61vourite_foods\x18\x06 \x03(\x0b\x32).pbbuildertest.Person.FavouriteFoodsEntry\x12\x0c\n\x04tags\x18\x07 \x03(\t\x12\x11\n\tlast_name\x18\x08 \x01(\t\x12\x12\n\nboolean_me\x18\t \x01(\x08\x12\"\n\x04logo\x18\n \x01(\x0b\x32\x14.pbbuildertest.Asset\x1a\x35\n\x13\x46\x61vouriteFoodsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"4\n\x05\x41sset\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\x0e\n\x06url_2x\x18\x02 \x01(\t\x12\x0e\n\x06url_3x\x18\x03 \x01(\tb\x06proto3"
11
+
12
+ pool = Google::Protobuf::DescriptorPool.generated_pool
13
+ pool.add_serialized_file(descriptor_data)
14
+
15
+ module Pbbuildertest
16
+ Person = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pbbuildertest.Person").msgclass
17
+ Asset = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pbbuildertest.Asset").msgclass
18
+ end
metadata CHANGED
@@ -1,29 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pbbuilder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bouke van der Bijl
8
+ - Julik Tarkhanov
9
+ - Stas Katkov
10
+ - Sebastian van Hesteren
8
11
  autorequire:
9
12
  bindir: bin
10
13
  cert_chain: []
11
- date: 2024-04-25 00:00:00.000000000 Z
14
+ date: 2025-06-19 00:00:00.000000000 Z
12
15
  dependencies:
13
16
  - !ruby/object:Gem::Dependency
14
17
  name: google-protobuf
15
18
  requirement: !ruby/object:Gem::Requirement
16
19
  requirements:
17
- - - "~>"
20
+ - - ">="
18
21
  - !ruby/object:Gem::Version
19
22
  version: '3.25'
23
+ - - "<"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.0'
20
26
  type: :runtime
21
27
  prerelease: false
22
28
  version_requirements: !ruby/object:Gem::Requirement
23
29
  requirements:
24
- - - "~>"
30
+ - - ">="
25
31
  - !ruby/object:Gem::Version
26
32
  version: '3.25'
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '5.0'
27
36
  - !ruby/object:Gem::Dependency
28
37
  name: activesupport
29
38
  requirement: !ruby/object:Gem::Requirement
@@ -66,9 +75,23 @@ dependencies:
66
75
  - - ">="
67
76
  - !ruby/object:Gem::Version
68
77
  version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ type: :development
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
69
92
  description:
70
93
  email:
71
- - bouke@cheddar.me
94
+ - julik@cheddar.me
72
95
  executables: []
73
96
  extensions: []
74
97
  extra_rdoc_files: []
@@ -85,17 +108,12 @@ files:
85
108
  - bin/appraisal
86
109
  - bin/test
87
110
  - gemfiles/.bundle/config
88
- - gemfiles/rails_6.gemfile
89
- - gemfiles/rails_6_1.gemfile
90
- - gemfiles/rails_7.gemfile
91
- - gemfiles/rails_7_0.gemfile
92
- - gemfiles/rails_7_1.gemfile
93
- - gemfiles/rails_head.gemfile
111
+ - gemfiles/rails_7_2.gemfile
112
+ - gemfiles/rails_8_0.gemfile
94
113
  - lib/pbbuilder.rb
95
114
  - lib/pbbuilder/collection_renderer.rb
96
115
  - lib/pbbuilder/errors.rb
97
116
  - lib/pbbuilder/handler.rb
98
- - lib/pbbuilder/pbbuilder.rb
99
117
  - lib/pbbuilder/protobuf_extension.rb
100
118
  - lib/pbbuilder/railtie.rb
101
119
  - lib/pbbuilder/template.rb
@@ -104,6 +122,8 @@ files:
104
122
  - test/pbbuilder_test.rb
105
123
  - test/protobuf_extension_test.rb
106
124
  - test/test_helper.rb
125
+ - test/test_proto.proto
126
+ - test/test_proto_pb.rb
107
127
  homepage: https://github.com/cheddar-me/pbbuilder
108
128
  licenses:
109
129
  - MIT
@@ -116,19 +136,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
136
  requirements:
117
137
  - - ">="
118
138
  - !ruby/object:Gem::Version
119
- version: '2.7'
139
+ version: '3.2'
120
140
  required_rubygems_version: !ruby/object:Gem::Requirement
121
141
  requirements:
122
142
  - - ">="
123
143
  - !ruby/object:Gem::Version
124
144
  version: '0'
125
145
  requirements: []
126
- rubygems_version: 3.3.7
146
+ rubygems_version: 3.5.9
127
147
  signing_key:
128
148
  specification_version: 4
129
- summary: Generate Protobuf Messages with a simple DSL similar to JBuilder
149
+ summary: Generate Protobuf messages with a simple DSL similar to JBuilder
130
150
  test_files:
131
151
  - test/pbbuilder_template_test.rb
132
152
  - test/pbbuilder_test.rb
133
153
  - test/protobuf_extension_test.rb
134
154
  - test/test_helper.rb
155
+ - test/test_proto.proto
156
+ - test/test_proto_pb.rb
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rake"
6
- gem "appraisal"
7
- gem "rails", "~> 7.0"
8
-
9
- gemspec path: "../"
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rake"
6
- gem "appraisal"
7
- gem "ruby-lsp"
8
- gem "rails", "~> 7.0.0"
9
-
10
- gemspec path: "../"
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rake"
6
- gem "appraisal"
7
- gem "ruby-lsp"
8
- gem "rails", "~> 7.1.0"
9
-
10
- gemspec path: "../"
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rake"
6
- gem "appraisal"
7
- gem "rails", github: "rails/rails", branch: "main"
8
-
9
- gemspec path: "../"
@@ -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)