Skip to content
embark edited this page Mar 22, 2017 · 15 revisions

Messages

Given the following compiled definition of a Foo::User:

# lib/foo/user.pb.rb
require 'protobuf/message'

module Foo
  class States < ::Protobuf::Enum
    define :ALABAMA, 1
    #...
  end

  class User < ::Protobuf::Message; end
  class Address < ::Protobuf::Message; end

  class User
    optional :string, :first_name, 1
    optional :string, :last_name, 2
    optional ::Foo::Address, :address, 3
    optional :bool, :is_active, 4
  end

  class Address
    optional :string, :street, 1
    optional :string, :city, 2
    optional ::Foo::States, :state, 3
    optional :int32, :zip_code, 4
  end
end

We can require the .pb.rb file and create user objects in a similar style to working with a Struct. New objects can use dot-notation or bracket-notation to get/set fields from the proto object. You can also instantiate new protos with a hash where keys are the field names. Any keys not corresponding to defined fields are silently ignored.

Whether using a the setter method or passing in values via hash, the value is processed by the same code that will reject bad values. Notice in the example below that we can assign fields of a Message type either with the object of that type or a hash. If a hash is given the type is simply instantiated with that hash and assigned.

require 'protobuf'
require 'lib/foo/user.pb'

user = Foo::User.new

# dot notation reading/writing fields
user.first_name = "Lloyd"
user.last_name = "Christmas"
user.first_name # => "Lloyd"
user.last_name # => "Christmas"

# bracket notation reading/writing fields
user[:first_name] = "Harry"
user[:last_name] = "Dunne"
user[:first_name] # => "Harry"
user[:last_name] # => "Dunne"

# assign whole message instances
user.address = Foo::Address.new({ :street => '21 Jump Street',
                                  :city => 'Alphaville',
                                  :state => Foo::States::CALIFORNIA,
                                  :zip_code => 12345 })

# or pass in the fields as a hash to the initializer (nested hashes are perfectly acceptable)
user = Foo::User.new({ :first_name => "Lloyd",
                       :last_name => "Christmas",
                       :address => { :street => '21 Jump Street',
                                     :city => 'Alphaville',
                                     :state => Foo::States::CALIFORNIA,
                                     :zip_code => 12345 } })
user.first_name # => Lloyd
user.last_name # => Christmas

Checking value presence

With a message instance you can check for the presence of certain values.

user = Foo::User.new(:first_name => 'Bob', :last_name => nil)
user.field?(:first_name)                   # => true
user.field?(:address)                      # => false
user.field?(:last_name)                    # => false (because we set the field to nil)
user.respond_to_has_and_present?(:last_name)  # => false (check if the field has been set and the value is "present?")

Predicate Boolean Fields

Any message that defines a boolean field will provide a predicate getter method in addition to the normal getter.

user = Foo::User.new
user.is_active     # => false
user.is_active?    # => false
user.is_active = true
user.is_active     # => true
user.is_active?    # => true

Enums

# lib/foo/user.pb.rb
require 'protobuf/message'

module Foo
  class States < ::Protobuf::Enum
    define :ALABAMA, 1
    define :ALASKA, 2
    define :ARIZONA, 3
    #...
  end
end

A class that inherits from Protobuf::Enum has a very simple interface. Any defined enum is instantiated and assigned as a class constant. Each Enum instance has a name and a tag (alternately accessed via to_i).

# Access an EnumValue directly by the named constant
Foo::States::ALABAMA               # => 1
Foo::States::ALABAMA.to_i          # => 1 (preferred method)
Foo::States::ALABAMA.tag           # => 1 
Foo::States::ALABAMA.name          # => :ALABAMA

Constant access is nice if you know the name, but if you only have the tag or name in a variable, use Enum.fetch to get the corresponding Enum instance. There are other access methods if you have an integer or a string/symbol name, but fetch handles all those cases (and more), so use it.

user.address.state                                   # => 1
enum_state = Foo::States.fetch(user.address.state)    # => Foo::State::ALABAMA

If you wish to programmatically access all of the defined values in an enum, you should use the .enums class method to get an array of all defined instances, or the .all_tags class method to get each unique defined tag. The class method .values has been deprecated and should be upgraded to access .enums or .all_tags depending on your use case.

Foo::States.enums            # => ["#<Protobuf::Enum(Foo::States)::ALABAMA=1>", "#<Protobuf::Enum(Foo::States)::ALASKA=2>", "#<Protobuf::Enum(Foo::States)::ARIZONA=3>"]
Foo::States.all_tags          # => [ 1, 2, 3 ]

Enum Aliases

Protocol Buffers provides the ability to enable aliases in an enum definition. As of v3.0.0.rc1, we can support this capability.

class StateMachine < ::Protobuf::Enum
  set_option :allow_alias
  define :ON, 1
  define :STARTED, 1
  define :OFF, 2
end
StateMachine.enums    # => ["#<Protobuf::Enum(StateMachine)::ON=1>","#<Protobuf::Enum(StateMachine)::STARTED=1>,"#<Protobuf::Enum(StateMachine)::OFF=2>""]
StateMachine.all_tags # => [ 1, 2 ]

Next: Serialization
Back: Compiling Definitions

Clone this wiki locally