Pbbuilder

PBBuilder generates Protobuf Messages with a simple DSL similar to JBuilder gem.

At least Rails 6.1 is required.

Compatibility with jBuilder

We don't aim to have 100% compitability and coverage with jbuilder gem, but we closely follow jbuilder's API design to maintain familiarity.

Jbuilder Pbbuilder
set!
cache!
cache_if!
cache_root!
fragment cache
extract!
merge!
child!
array!
.call

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.

Usage

The main difference is that it can use introspection to figure out what kind of protobuf message it needs to create.

This is an example .proto message.

message Person {
  string name = 1;
  repeated Person friends = 2;
}

The following .pb file would generate a message of valid Person type.

person = RPC::Person.new

Pbbuilder.new(person) do |pb|
  pb.name "Hello"
  pb.friends [1, 2, 3] do |number|
    pb.name "Friend ##{number}"
  end
end

Under the hood, this DSL is using method_missing and set! methods. But there are other methods and features to use.

extract!

The following _account.pb.pbbuilder partial:

pb.id .id
pb.phone_number .phone_number
pb.tag .tag

could be rewritten to a shorter version with a use of extract!.

pb.extract! , :id, :phone_number, :tag

Partials

Given partial _account.pb.pbuilder:

pb.name .name
pb.registration_date .created_at

Using partial while passing a variable to it

pb. partial: "account", account: @account

Here is way to use partials with collection while passing a variable to it

pb.accounts @accounts, partial: "account", as: 

Collections (or Arrays)

There are two different methods to render a collection. One that uses ActiveView::CollectionRenderer

pb.friends partial: "racers/racer", as: :racer, collection: @racers
pb.friends "racers/racer", as: :racer, collection: @racers

And there are other ways, that don't use Collection Renderer (not very effective probably)

pb.partial! @racer, racer: Racer.new(123, "Chris Harris", friends)
pb.friends @friends, partial: "racers/racer", as: :racer

Caching

it uses Rails.cache and works like caching in HTML templates:

pb.cache! "cache-key", expires_in: 10.minutes do
  pb.name @person.name
end

You can also conditionally cache a block by using cache_if! like this:

pb.cache_if! !admin?, "cache-key", expires_in: 10.minutes do
  pb.name @person.name
end

Fragment caching currently works through ActionView::CollectionRenderer and can be used only with the following syntax:

pb.friends partial: "racers/racer", as: :racer, collection: @racers, cached: true
pb.friends "racers/racer", as: :racer, collection: @racers, cached: true

Installation

Add this line to your application's Gemfile:

gem 'pbbuilder'

And then execute:

$ bundle

Or install it yourself as:

$ gem install pbbuilder

Development

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.

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.

Testing

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

To run only one tests from file - use m utility. Like this: bundle exec appraisal rails-7-0 m test/pbbuilder_template_test.rb:182

Contributing

Everyone is welcome to contribute.

License

The gem is available as open source under the terms of the MIT License.