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 +4 -4
- data/.github/workflows/test.yml +3 -15
- data/.gitignore +1 -1
- data/Appraisals +6 -8
- data/CHANGELOG.md +19 -27
- data/Gemfile +0 -2
- data/README.md +35 -19
- data/Rakefile +22 -5
- data/gemfiles/{rails_6_1.gemfile → rails_7_2.gemfile} +2 -2
- data/gemfiles/{rails_6.gemfile → rails_8_0.gemfile} +2 -1
- data/lib/pbbuilder/errors.rb +2 -4
- data/lib/pbbuilder/template.rb +19 -20
- data/lib/pbbuilder.rb +24 -11
- data/pbbuilder.gemspec +8 -7
- data/test/pbbuilder_template_test.rb +2 -2
- data/test/pbbuilder_test.rb +1 -1
- data/test/test_helper.rb +13 -21
- data/test/test_proto.proto +27 -0
- data/test/test_proto_pb.rb +18 -0
- metadata +37 -15
- data/gemfiles/rails_7.gemfile +0 -9
- data/gemfiles/rails_7_0.gemfile +0 -10
- data/gemfiles/rails_7_1.gemfile +0 -10
- data/gemfiles/rails_head.gemfile +0 -9
- data/lib/pbbuilder/pbbuilder.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f241a460f2ab7e8383c37db199821f8cb5e8d2f69c262f38c7c462a4d7827e0
|
4
|
+
data.tar.gz: 3e8c43a8d0c0362049a9cb8a709c86eac64d86e12366812f2b996b1e81d73c23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '03691d015507cb98d862c4f1c352a08c20e0968f0fae3a23795bec6e6ffdbef6b3e54dca9057364c32e353bfc561e8f117cf02219ad2c73725ae004b7fd1590b'
|
7
|
+
data.tar.gz: b52b5324090887effbaba0ae6ff79ebe9cc7c08ffae945037883d42375bca4ec53bd5a37dc4a9d144c488a6ba5e7ffce88d05f3e9f7ba1a260af5f0ed55107ed
|
data/.github/workflows/test.yml
CHANGED
@@ -13,24 +13,12 @@ jobs:
|
|
13
13
|
strategy:
|
14
14
|
fail-fast: false
|
15
15
|
matrix:
|
16
|
-
ruby: ["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@
|
21
|
+
- uses: actions/checkout@v4
|
34
22
|
|
35
23
|
- uses: ruby/setup-ruby@v1
|
36
24
|
with:
|
data/.gitignore
CHANGED
data/Appraisals
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
appraise "rails-
|
2
|
-
gem "rails", "~>
|
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-
|
6
|
-
gem "rails", "~>
|
7
|
-
|
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
|
-
|
2
|
-
All notable changes to this project will be documented in this file.
|
1
|
+
## Unreleased
|
3
2
|
|
4
|
-
|
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
|
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
|
47
|
-
### Added
|
46
|
+
## 0.13.1
|
48
47
|
- #merge! to support boolean values
|
49
48
|
|
50
|
-
## 0.13.0
|
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
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
|
-
|
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
|
-
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
160
|
-
|
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.
|
15
|
-
|
16
|
-
|
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
|
data/lib/pbbuilder/errors.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
data/lib/pbbuilder/template.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
113
|
-
# There is no need to know the entire "stack" of nodes. ActionView::CollectionRenderer
|
114
|
-
#
|
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]
|
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
|
177
|
-
#
|
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/
|
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
|
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
|
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
|
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
|
-
#
|
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.
|
6
|
-
spec.authors = ["Bouke van der Bijl"]
|
7
|
-
spec.email = ["
|
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
|
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
|
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", "
|
17
|
+
spec.add_dependency "google-protobuf", ">= 3.25", "< 5.0"
|
18
18
|
spec.add_dependency "activesupport"
|
19
|
-
spec.add_development_dependency
|
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",
|
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
|
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
|
data/test/pbbuilder_test.rb
CHANGED
@@ -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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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.
|
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:
|
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
|
-
-
|
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/
|
89
|
-
- gemfiles/
|
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
|
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.
|
146
|
+
rubygems_version: 3.5.9
|
127
147
|
signing_key:
|
128
148
|
specification_version: 4
|
129
|
-
summary: Generate Protobuf
|
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
|
data/gemfiles/rails_7.gemfile
DELETED
data/gemfiles/rails_7_0.gemfile
DELETED
data/gemfiles/rails_7_1.gemfile
DELETED
data/gemfiles/rails_head.gemfile
DELETED
data/lib/pbbuilder/pbbuilder.rb
DELETED