pbbuilder 0.13.3 → 0.15.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: 10736ed13e1665f1bf45095973eb464ab2bcef87f3d69fe4bb8f89e3f35e0474
4
- data.tar.gz: a2a8522c756cdf99638d7aef7f3ad7ed9d009ab24b46e499f2f7e0d4a3427b5e
3
+ metadata.gz: e89f023f27d95d8d11cdf454148ef955b0234b6d7e59f40e30859e09c02761cb
4
+ data.tar.gz: 108693d16d963bc46ad96d3f79ed4b8078f8efc9c3a7f798c32b7bdd5cc98e73
5
5
  SHA512:
6
- metadata.gz: 366ce4bf8ef0195f67f9961cd15b5ebc8311b3c15c192d54a5a064a33a34e9e7696a2114afab7f4734bb1e89d61769ae823a91567562741bfc4bb0e9a41a4d3d
7
- data.tar.gz: 88d5bc81285ac6e48531328b03a2194b5939dd40ca8bfe003502486f7009c22bb55f1a18c2767f28153942801c7f913526d5a5c18294373a00fcffbfd6daeef4
6
+ metadata.gz: aa478fc7fad5954801385b731b7fc77296d732e8f13a6bc5688417dfdeb247a7135f3ecbce14f88abc441a87cbc6ad669d3f5547b917f80bfddfbac7e0b2c9a8
7
+ data.tar.gz: 2fe79ab3cfde6b908907d55eaf37054cb0b530f4e98dbf44d732bef82c46054f0efd4c6ba3a05827915cc855f9cc70a671adc2316c691edd5147ababec573ec6
data/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@ 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.15.0
7
+ ### Changed
8
+ - #merge! method was refactored to accomodate caching for all data types (especially those that are :repeated)
9
+
10
+ ## 0.14.0
11
+ ### Added
12
+ - Adding `frozen_string_literal: true` to all files.
13
+
6
14
  ## 0.13.2 2023.02.3
7
15
  ### Fixed
8
16
  - 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.
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
5
  # Specify your gem's dependencies in pbbuilder.gemspec.
data/README.md CHANGED
@@ -1,21 +1,26 @@
1
1
  # Pbbuilder
2
2
  PBBuilder generates [Protobuf](https://developers.google.com/protocol-buffers) Messages with a simple DSL similar to [JBuilder](https://rubygems.org/gems/jbuilder) gem.
3
3
 
4
- ## Usage
5
- It basically works exactly like jbuilder. The main difference is that it can use introspection to figure out what kind of protobuf message it needs to create.
4
+ ## Compatibility
5
+ We don't aim to have 100% compatibility with jbuilder gem, but we closely follow jbuilder's API design.
6
+
7
+ | | Jbuilder | Pbbuilder |
8
+ |---|---|---|
9
+ | set! | ✅ | ✅ |
10
+ | cache! | ✅ | ✅ |
11
+ | cache_if! | ✅ | ✅ |
12
+ | cache_root! | ✅| |
13
+ | extract! | ✅ | ✅ |
14
+ | merge! | ✅ | ✅ |
15
+ | deep_format_keys! | ✅ | |
16
+ | child! | ✅ | |
17
+ | array! | ✅ | |
18
+ | ignore_nil! | ✅ | |
6
19
 
20
+ ## Usage
21
+ The main difference is that it can use introspection to figure out what kind of protobuf message it needs to create.
7
22
 
8
- Following Pbbuilder code
9
- ```
10
- person = RPC::Person.new
11
- Pbbuilder.new(person) do |pb|
12
- pb.name "Hello"
13
- pb.friends [1, 2, 3] do |number|
14
- pb.name "Friend ##{number}"
15
- end
16
- end
17
- ```
18
- Would produce this message:
23
+ This is an example `.proto` message.
19
24
 
20
25
  ```
21
26
  message Person {
@@ -24,6 +29,52 @@ message Person {
24
29
  }
25
30
  ```
26
31
 
32
+ The following `.pb` file would generate a message of valid Person type.
33
+ ```
34
+ person = RPC::Person.new
35
+
36
+ Pbbuilder.new(person) do |pb|
37
+ pb.name "Hello"
38
+ pb.friends [1, 2, 3] do |number|
39
+ pb.name "Friend ##{number}"
40
+ end
41
+ end
42
+ ```
43
+
44
+ Under the hood, this DSL is using `method_missing` and `set!` methods. But there are other methods and features to use.
45
+
46
+ ### extract!
47
+ The following `_account.pb.pbbuilder` partial:
48
+ ```
49
+ pb.id account.id
50
+ pb.phone_number account.phone_number
51
+ pb.tag account.tag
52
+ ```
53
+
54
+ could be rewritten to a shorter version with a use of `extract!`.
55
+ ```
56
+ pb.extract! account, :id, :phone_number, :tag
57
+ ```
58
+
59
+ ### Partials
60
+ Given partial `_account.pb.pbuilder`:
61
+
62
+ ```
63
+ pb.name account.name
64
+ pb.registration_date account.created_at
65
+ ```
66
+
67
+ Using partial while passing a variable to it
68
+
69
+ ```
70
+ pb.account partial: "account", account: @account
71
+ ```
72
+
73
+ Here is way to use partials with collection while passing a variable to it
74
+
75
+ ```
76
+ pb.accounts @accounts, partial: "account", as: account
77
+ ```
27
78
 
28
79
  ### Caching
29
80
  Fragment caching is supported, it uses Rails.cache and works like caching in HTML templates:
@@ -42,7 +93,6 @@ pb.cache_if! !admin?, "cache-key", expires_in: 10.minutes do
42
93
  end
43
94
  ```
44
95
 
45
-
46
96
  ## Installation
47
97
  Add this line to your application's Gemfile:
48
98
 
@@ -59,10 +109,20 @@ Or install it yourself as:
59
109
  ```bash
60
110
  $ gem install pbbuilder
61
111
  ```
112
+ ## Development
62
113
 
63
- ## Contributing
114
+ 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.
64
115
 
65
- When debugging, make sure you're prepending `::Kernel` to any calls such as `puts` as otherwise the code will think you're trying to add another attribute onto the protobuf.
116
+ 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).
117
+
118
+ ## Testing
119
+ 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`
120
+
121
+ To run only one tests from file - use `m` utility. Like this:
122
+ `bundle exec appraisal rails-7-0 m test/pbbuilder_template_test.rb:182`
123
+
124
+ ## Contributing
125
+ Everyone is welcome to contribute.
66
126
 
67
127
  ## License
68
128
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/setup"
2
4
  require "bundler/gem_tasks"
3
5
  require "rake/testtask"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pbbuilder'
2
4
 
3
5
  class Pbbuilder
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pbbuilder/template"
2
4
 
3
5
  # Basically copied and pasted from JbuilderHandler, except it uses Pbbuilder
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Pbbuilder = Class.new(begin
2
4
  require 'active_support/proxy_object'
3
5
  ActiveSupport::ProxyObject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "google/protobuf/message_exts"
2
4
 
3
5
  module Google::Protobuf::MessageExts::ClassMethods
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails'
2
4
  require "pbbuilder/handler"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # PbbuilderTemplate is an extension of Pbbuilder to be used as a Rails template
2
4
  # It adds support for partials.
3
5
  class PbbuilderTemplate < Pbbuilder
data/lib/pbbuilder.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pbbuilder/pbbuilder"
2
4
  require 'pbbuilder/errors'
3
5
  require "pbbuilder/protobuf_extension"
@@ -114,33 +116,58 @@ class Pbbuilder
114
116
  ::Kernel.raise Pbbuilder::MergeError.build(target!, object) unless object.class == ::Hash
115
117
 
116
118
  object.each_key do |key|
117
- if object[key].respond_to?(:empty?) && object[key].empty?
118
- ::Kernel.raise Pbbuilder::MergeError.build(target!, object)
119
- end
119
+ next if object[key].respond_to?(:empty?) && object[key].empty?
120
120
 
121
- if object[key].class == ::String
122
- # pb.fields {"one" => "two"}
123
- @message[key.to_s] = object[key]
124
- elsif object[key].class == ::Array
125
- # pb.tags ['test', 'ok']
126
- @message[key.to_s].replace object[key]
127
- elsif object[key].class == ::TrueClass || object[key].class == ::FalseClass
128
- # pb.boolean true || false
129
- @message[key.to_s] = object[key]
130
- elsif ( obj = object[key]).class == ::Hash
131
- # pb.field_name do
132
- # pb.tags ["ok", "cool"]
133
- # end
134
- #
121
+ descriptor = @message.class.descriptor.lookup(key.to_s)
122
+ ::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?
135
123
 
124
+ if descriptor.label == :repeated
136
125
  # optional empty fields don't show up in @message object,
137
126
  # we recreate empty message, so we can fill it with values
138
127
  if @message[key.to_s].nil?
139
- field_descriptor = @message.class.descriptor.lookup(key.to_s)
140
- @message[key.to_s] = _new_message_from_descriptor(field_descriptor)
128
+ @message[key.to_s] = _new_message_from_descriptor(descriptor)
129
+ end
130
+
131
+ if object[key].respond_to?(:to_hash)
132
+ object[key].to_hash.each {|k, v| @message[key.to_s][k] = v}
133
+ elsif object[key].respond_to?(:to_ary)
134
+ elements = object[key].map do |obj|
135
+ descriptor.subtype ? descriptor.subtype.msgclass.new(obj) : obj
136
+ end
137
+
138
+ @message[key.to_s].replace(elements)
141
139
  end
140
+ else
141
+ if object[key].class == ::String
142
+ # pb.fields {"one" => "two"}
143
+ @message[key.to_s] = object[key]
144
+ elsif object[key].class == ::TrueClass || object[key].class == ::FalseClass
145
+ # pb.boolean true || false
146
+ @message[key.to_s] = object[key]
147
+ elsif object[key].class == ::Array
148
+ # pb.field_name do
149
+ # pb.tags ["ok", "cool"]
150
+ # end
151
+
152
+ @message[key.to_s] = object[key]
153
+ elsif object[key].class == ::Hash
154
+ if @message[key.to_s].nil?
155
+ @message[key.to_s] = _new_message_from_descriptor(descriptor)
156
+ end
142
157
 
143
- @message[key.to_s] = _scope(@message[key.to_s]) { self.merge!(obj) }
158
+ object[key].each do |k, v|
159
+ if object[key][k].respond_to?(:to_hash)
160
+ _scope(@message[key.to_s][k.to_s]) { self.merge!(object[key][k]) }
161
+ elsif object[key][k].respond_to?(:to_ary)
162
+ @message[key.to_s][k.to_s].replace object[key][k]
163
+ else
164
+ # Throws an error, if we try to merge nil object into empty value.
165
+ next if object[key][k].nil? && @message[key.to_s][k.to_s].nil?
166
+
167
+ @message[key.to_s][k.to_s] = object[key][k]
168
+ end
169
+ end
170
+ end
144
171
  end
145
172
  end
146
173
  end
data/pbbuilder.gemspec CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Gem::Specification.new do |spec|
2
4
  spec.name = "pbbuilder"
3
- spec.version = "0.13.3"
5
+ spec.version = "0.15.0"
4
6
  spec.authors = ["Bouke van der Bijl"]
5
7
  spec.email = ["bouke@cheddar.me"]
6
8
  spec.homepage = "https://github.com/cheddar-me/pbbuilder"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "test_helper"
2
4
  require "action_view/testing/resolvers"
3
5
 
@@ -101,13 +103,13 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
101
103
  end
102
104
 
103
105
  test "should raise Error in merge! an empty hash" do
104
- assert_raise(ActionView::Template::Error) {
106
+ assert_nothing_raised {
105
107
  render(<<-PBBUILDER)
106
108
  pb.merge! "name" => {}
107
109
  PBBUILDER
108
110
  }
109
111
 
110
- assert_raise(ActionView::Template::Error) {
112
+ assert_nothing_raised {
111
113
  render(<<-PBBUILDER)
112
114
  pb.merge! "" => {}
113
115
  PBBUILDER
@@ -129,6 +131,39 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
129
131
  assert_equal "suslik", result["name"]
130
132
  end
131
133
 
134
+ test "caching repeated partial" do
135
+ template = <<-PBBUILDER
136
+ pb.cache! "some-random-key" do
137
+ pb.friends @friends, partial: "racers/racer", as: :racer
138
+ end
139
+ PBBUILDER
140
+ friends = [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
141
+
142
+ result = assert_nothing_raised { render(template, friends: friends) }
143
+ assert_equal("Johnny Test", result.friends[0].name)
144
+ assert_equal("Max Verstappen", result.friends[1].name)
145
+
146
+ result = render('pb.cache! "some-random-key" do; end ')
147
+ assert_equal("Johnny Test", result.friends[0].name)
148
+ assert_equal("Max Verstappen", result.friends[1].name)
149
+ end
150
+
151
+ test "caching map values" do
152
+ template = <<-PBBUILDER
153
+ pb.cache! "some-random-cache-key" do
154
+ pb.favourite_foods @foods
155
+ end
156
+ PBBUILDER
157
+
158
+ r = assert_nothing_raised { render( template, foods: {'pizza' => 'yes', 'borsh' => 'false'})}
159
+ assert_equal('false', r.favourite_foods['borsh'])
160
+ assert_equal('yes', r.favourite_foods['pizza'])
161
+
162
+ result = render('pb.cache! "some-random-cache-key" do; end ')
163
+ assert_equal('false', result.favourite_foods['borsh'])
164
+ assert_equal('yes', result.favourite_foods['pizza'])
165
+ end
166
+
132
167
  test "object fragment caching" do
133
168
  render(<<-PBBUILDER)
134
169
  pb.cache! "cache-key" do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "test_helper"
2
4
 
3
5
  class PbbuilderTest < ActiveSupport::TestCase
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "test_helper"
2
4
 
3
5
  class ProtobufExtensionTest < ActiveSupport::TestCase
data/test/test_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/setup"
2
4
 
3
5
  require "rails"
@@ -15,6 +17,7 @@ require "google/protobuf"
15
17
  require "google/protobuf/field_mask_pb"
16
18
 
17
19
  require "active_support/testing/autorun"
20
+ require "pry"
18
21
 
19
22
  ActiveSupport.test_order = :random
20
23
 
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.13.3
4
+ version: 0.15.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: 2023-02-13 00:00:00.000000000 Z
11
+ date: 2023-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf