Class: Pbbuilder
- Inherits:
- BasicObject
- Defined in:
- lib/pbbuilder.rb,
lib/pbbuilder/errors.rb,
lib/pbbuilder/railtie.rb,
lib/pbbuilder/collection_renderer.rb
Overview
Pbbuilder makes it easy to create a protobuf message using the builder pattern. It is heavily inspired by jbuilder
Given this example message definition: message Person
string name = 1;
repeated Person friends = 2;
You can use Pbbuilder as follows:
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
message.name => “Hello”
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.
Direct Known Subclasses
Defined Under Namespace
Classes: CollectionRenderer, MergeError, Railtie
Instance Method Summary collapse
- #attributes! ⇒ Object
-
#extract!(element, *args) ⇒ Object
Shorthand command for getting a few attributes from an object.
-
#initialize(message) {|_self| ... } ⇒ Pbbuilder
constructor
A new instance of Pbbuilder.
-
#merge!(object) ⇒ Object
Merges object into a protobuf message, mainly used for caching.
- #method_missing ⇒ Object
- #new_message_for(field) ⇒ Object
- #respond_to_missing?(field) ⇒ Boolean
-
#set!(field, *args, &block) ⇒ Object
When calling for ex: response.drivers, where response is a Google::Protobuf object, ‘drivers’ is not a method on that.
-
#target! ⇒ Object
Initialized message object.
Constructor Details
#initialize(message) {|_self| ... } ⇒ Pbbuilder
Returns a new instance of Pbbuilder.
33 34 35 36 37 |
# File 'lib/pbbuilder.rb', line 33 def initialize() = yield self if ::Kernel.block_given? end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing ⇒ Object
39 40 41 |
# File 'lib/pbbuilder.rb', line 39 def method_missing(...) set!(...) end |
Instance Method Details
#attributes! ⇒ Object
43 44 45 |
# File 'lib/pbbuilder.rb', line 43 def attributes! .to_h end |
#extract!(element, *args) ⇒ Object
Shorthand command for getting a few attributes from an object.
pb.extract! racer, :name, :id, :age
139 140 141 |
# File 'lib/pbbuilder.rb', line 139 def extract!(element, *args) args.each { |arg| [arg.to_s] = element.send(arg) } end |
#merge!(object) ⇒ Object
Merges object into a protobuf message, mainly used for caching.
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/pbbuilder.rb', line 146 def merge!(object) ::Kernel.raise Pbbuilder::MergeError.build(target!, object) unless object.class == ::Hash object.each do |key, value| next if value.respond_to?(:empty?) && value.empty? descriptor = _descriptor_for_field(key) ::Kernel.raise ::ArgumentError, "Unknown field #{key}" if descriptor.nil? if descriptor.label == :repeated # optional empty fields don't show up in @message object, # we recreate empty message, so we can fill it with values if [key.to_s].nil? [key.to_s] = (descriptor) end if value.respond_to?(:to_hash) value.to_hash.each {|k, v| [key.to_s][k] = v} elsif value.respond_to?(:to_ary) elements = value.map do |obj| descriptor.subtype ? descriptor.subtype.msgclass.new(obj) : obj end [key.to_s].replace(elements) end else if descriptor.type == :message [key.to_s] = descriptor.subtype.msgclass.new(value) else # pb.fields {"one" => "two"} # pb.boolean true || false # pb.field_name do # pb.tags ["ok", "cool"] # end [key.to_s] = value end end end end |
#new_message_for(field) ⇒ Object
193 194 195 196 197 198 |
# File 'lib/pbbuilder.rb', line 193 def (field) descriptor = _descriptor_for_field(field) ::Kernel.raise ::ArgumentError, "Unknown field #{field}" if descriptor.nil? (descriptor) end |
#respond_to_missing?(field) ⇒ Boolean
47 48 49 |
# File 'lib/pbbuilder.rb', line 47 def respond_to_missing?(field) !!_descriptor_for_field(field) end |
#set!(field, *args, &block) ⇒ Object
When calling for ex: response.drivers, where response is a Google::Protobuf object, ‘drivers’ is not a method on that. These methods (or messages in our case) get added here. This is of course based on what kind of message it is. Singular, an array (repeated) etc. with their arguments.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/pbbuilder.rb', line 54 def set!(field, *args, &block) name = field.to_s descriptor = _descriptor_for_field(name) ::Kernel.raise ::ArgumentError, "Unknown field: #{name}" if descriptor.nil? # An block is used to pass on it's children if ::Kernel.block_given? ::Kernel.raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message if descriptor.label == :repeated # example syntax that should end up here: # pb.field @array { |element| pb.name element.name } ::Kernel.raise ::ArgumentError, "wrong number of arguments #{args.length} (expected 1)" unless args.length == 1 collection = args.first _append_repeated(name, descriptor, collection, &block) else # example syntax that should end up here: # pb.field { pb.name "hello" } ::Kernel.raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty? = ([name] ||= (descriptor)) _scope(, &block) end # No block given, but with 1 argument elsif args.length == 1 arg = args.first if descriptor.label == :repeated if arg.respond_to?(:to_hash) # example syntax that should end up here: # pb.fields {"one" => "two"} arg.to_hash.each { |k, v| [name][k] = v } elsif arg.respond_to?(:to_ary) && !descriptor.type.eql?(:message) # pb.fields ["one", "two"] [name].replace arg.to_ary elsif arg.respond_to?(:to_ary) && descriptor.type.eql?(:message) # pb.friends [Person.new(name: "Johnny Test"), Person.new(name: "Max Verstappen")] # # Accepts both objects that can be to_hash-translated into keyword arguments # for creating a nested proto message object and actual proto message objects # which can be assigned "as is". With "as-is" assignment the proto message can be stored # in memory and reused = arg.map do || # If the arg passed already is a proto message and is the same as the # field expects - just use it as-is if .is_a?(descriptor.subtype.msgclass) else descriptor.subtype.msgclass.new() end end [name].replace() else # example syntax that should end up here: # pb.fields "one" [name].replace [arg] end else # example syntax that should end up here: # pb.field "value" [name] = arg end else # example syntax that should end up here: # pb.field @value, :id, :name, :url element = args.shift if descriptor.label == :repeated # If the message field that's being assigned is a repeated field, then we assume that `element` is enumerable. # This way you can do something like pb.repeated_field @array, :id, :name # This will create a message out of every object in @array, copying over the :id and :name values. set!(name, element) do |item| extract!(item, *args) end else set!(name) do extract!(element, *args) end end end end |
#target! ⇒ Object
Returns Initialized message object.
188 189 190 |
# File 'lib/pbbuilder.rb', line 188 def target! end |