このページは Protocol Buffers 公式リファレンス Ruby Generated Codeの和訳です。原文はCreative Commons Attribution 4.0 Licenseで公開されており、ソースコードはApache Licenseで公開されています。この訳文もそれにならいます。
Ruby Generated Code
このページでは、プロトコルバッファコンパイラが任意のプロトコル定義に対して生成するメッセージオブジェクトのAPIについて説明します。 このドキュメントを読む前に、proto3言語ガイドを読むことをお勧めします。
現時点ではまだproto3だけしかサポートされていません。proto2のサポートも計画されていますが、まだ利用できません1。
Rubyのプロトコルコンパイラはメッセージスキーマを定義するDSLからRubyのソースファイルを生成します。DSLは引き続き変更される可能性があります(特にproto2サポートなどの機能の追加時)が、 このガイドでは、DSLではなく生成されたメッセージのAPIのみを説明します。
コンパイラ呼び出し
プロトコルバッファコンパイラは--ruby_out=
というコマンドラインフラグをつけて実行することでRubyのコードを生成します。--ruby_out=
オプションにはコンパイラにRubyコードを出力させたいディレクトリを直接指定します。コンパイラはそれぞれの.proto
ファイルの入力に対して、.rb
ファイルを作成します。出力ファイルの名前は.proto
ファイルの名前から取られますが、二点違いがあります。
- 拡張子 (
.proto
)は_pb.rb
で置き換えられます。 - (
--proto_path=
か-I
のコマンドラインオプションで指定された)protoのパスは、(--ruby_out=
フラグで指定された)出力パスに置き換えられます。
たとえば、次のようなコマンドを実行したとします。
protoc --proto_path=src --ruby_out=build/gen src/foo.proto src/bar/baz.proto
コンパイラはsrc/foo.proto
とsrc/bar/baz.proto
の入力から、build/gen/foo_pb.rb
とbuild/gen/bar/baz_pb.rb
の2つの出力ファイルを生成します。 コンパイラは必要に応じてディレクトリbuild/gen/bar
を自動的に作成しますが、buildまたはbuild/genは作成しません。それらはすでに作成済みである必要があります。
パッケージ
.proto
ファイルで定義されたパッケージ名は生成されたメッセージ型のモジュール構造を生成するために使われます。
このようなファイルがある場合、
package foo_bar.baz;
message MyMessage {}
プロトコルコンパイラはFooBar::Baz::MyMessage
という名前のメッセージ型を出力します。
メッセージ型
このようなシンプルなメッセージ宣言に対して、
message Foo {}
プロトコルバッファコンパイラはFoo
という名前のクラスを生成します。生成されたクラスはObject
クラスを継承しています(protoで共通の基底クラスはありません)。C++やJavaとは違って、Rubyが生成したコードは.proto
ファイルのoptimize_for
オプションの影響を受けません。実際のところ、Rubyコードの最適化対象は常にコードサイズとなっています
Foo
サブクラスを作成すべきではありません。生成されたクラスはサブクラス用に設計されていないため、「脆弱な基底クラス」の問題2を引き起こす可能性があります。
Rubyのメッセージクラスは各フィールドに対するアクセサを定義します。また、次にあげる標準メソッドを提供します。
-
Message#dup
,Message#clone
: このメッセージのシャローコピーを行い、新しく作られたコピーを返します。 -
Message#==
: 2つのメッセージの完全な等価比較を行います。 -
Message#hash
: メッセージの値のシャローハッシュ値を計算します。 -
Message#to_hash
,Message#to_h
: メッセージオブジェクトをRubyのHashオブジェクトに変換します。最上位のメッセージだけが変換されます。 -
Message#inspect
: このメッセージを表す可読性のある表現の文字列を返します。 -
Message#[]
,Message#[]=
: 文字列の名前でフィールドを取得または設定します。将来的には、これはおそらくget/set拡張にも使用されるでしょう。
このメッセージクラスはまた次のような静的メソッドも定義します。(通常のメソッドだと.protoファイルで定義されるものと競合する可能性があるので、なるべく静的メソッドで実装するようにされています)
-
Message.decode(str)
: このメッセージのプロトコルバッファバイナリをデコードし、新しいインスタンスを返します。 -
Message.encode(proto)
: このクラスのメッセージオブジェクトをバイナリ文字列にシリアライズします。 -
Message.decode_json(str)
: このメッセージのJSON文字列をデコードして、新しいインスタンスとして返します。 -
Message.encode_json(proto)
: このクラスのメッセージオブジェクトをJSON文字列にシリアライズします。 -
Message.descriptor
: このメッセージオブジェクトのGoogle::Protobuf::Descriptor
を返します。
このメッセージを作る場合、コンストラクタのフィールドで簡単に初期化できます。次にメッセージの作成と使用の例を示します。
message = MyMessage.new(:int_field => 1,
:string_field => "String",
:repeated_int_field => [1, 2, 3, 4,
:submessage_field => SubMessage.new(:foo => 42))
serialized = MyMessage.encode(message)
message2 = MyMessage.decode(serialized)
raise unless message2.int_field == 1
メッセージは別のメッセージの中にも宣言できます。 例: message Foo { message Bar { } }
この場合、Bar
クラスはFoo
クラスの中で宣言されるので、Foo::Bar
として参照できます。
フィールド
メッセージ型の各フィールドには、フィールドに対してget/setするアクセサメソッドがあります。したがって、fooフィールドを指定すると、次のように記述できます。
message.foo = get_value()
print message.foo
フィールドを値を入れるたびに、そのフィールドで宣言された型に対して値が型チェックされます。 値の型が間違っている(または範囲外の)場合、例外が発生します。
単数フィールド (proto3)
単数のプリミティブなフィールド(数値、文字列、真偽値)の場合、セットする値はそのフィールドへの正しい型であるべきであり、適切な範囲内でなくてはなりません。
- 数値: 値は
Fixnum
、Bignum
もしくはFloat
であるべきです。 セットする値はそのフィールドで正確に表現できる型である必要があります。なのでint32
のフィールドに1.0
を入れても構いませんが、1.2は入れられません。 - 真偽型フィールド: 値は
true
もしくはfalse
でなくてはなりません。 他の値の場合、暗黙的にtrue
/false
に変換されることはありません。 - バイト配列フィールド: セットする値はStringオブジェクトである必要があります。 プロトコルバッファライブラリは文字列を複製し、ASCII-8BITエンコーディングに変換し、
freeze
します。 - 文字列フィールド: セットする値はStringオブジェクトである必要があります。 プロトコルバッファライブラリは文字列を複製し、UTF8エンコーディングに変換し、
freeze
します。
自動変換を実行してくれるようおな、自動の#to_s
, #to_i
などの呼び出しはありません。もし必要であれば、自分で値を変換する必要があります
proto3は、単数非メッセージフィールドが明示的に設定されているかどうか、確認する方法を提供しないため、戻り値が0 / false / ""である場合は、その値がどこかで設定されたかデフォルト値のままということになります。
単数メッセージフィールド
フィールドの型がメッセージの場合、未設定であればフィールドはnil
を返すため、メッセージが明示的に設定されたかどうかをいつでも確認できます。 値を明示的にnil
に設定して、そのフィールドをクリアすることもできます。
if message.submessage_field.nil?
puts "submessage フィールドは未設定です"
else
message.submessage_field = nil
puts "submessageフィールドはクリアされました"
end
メッセージをセットするには、正しい型で生成されたメッセージオブジェクトである必要があります。
メッセージをセットする際に、メッセージを循環させることもできます。
例です。
// foo.proto
message RecursiveMessage {
RecursiveMessage submessage = 1;
}
# test.rb
require 'foo'
message = RecursiveSubmessage.new
message.submessage = message
もしこれをシリアライズすると、ライブラリは循環を検知して、シリアライズを失敗させます。
Repeatedフィールド
RepeatedフィールドはGoogle::Protobuf::RepeatedField
のカスタムクラスを使って表現されます。このクラスはRubyのArray
のように振る舞い、Enumerable
モジュールをmix-inしています。通常のRuby配列とは異なり、RepeatedField
は特定の型から初期化され、配列のメンバーは全て正しい型でなくてはなりません。型と範囲はメッセージフィールドと同様にチェックされます。
int_repeatedfield = Google::Protobuf::RepeatedField.new(:int32, [1, 2, 3])
# TypeErrorが発生
int_repeatedfield[2] = "not an int32"
# RangeErrorが発生
int_repeatedfield[2] = 2**33
message.int32_repeated_field = int_repeatedfield
# 許可されない。通常のRuby配列は型を強制できない
message.int32_repeated_field = [1, 2, 3, 4]
# これは通る。様子が安全な型の配列にコピーされるため
message.int32_repeated_field += [1, 2, 3, 4]
RepeatedField
型は、通常のRuby配列と同じメソッドをすべてサポートしています。repeated_field.to_a
でいつものArrayクラスに変換できます。
map フィールド
mapフィールドは、RubyのHash
クラスのように動作する特別なクラス(Google::Protobuf::Map
)を使って表されます。 通常のHash
とは異なり、Map
は決まった型のキーと値で初期化され、Map
のすべてのキーと値は正しい型でなくてはなりません。 型と範囲は、メッセージクラスのフィールドやRepeatedField
の要素と同様にチェックされます。
int_string_map = Google::Protobuf::Map.new(:int32, :string)
# mapに要素がない場合、nilを返します
print int_string_map[5]
# 値は文字列でなければいけないのでTypeErrorが発生します
int_string_map[11] = 200
# OK
int_string_map[123] = "abc"
message.int32_string_map_field = int_string_map
列挙型
Rubyには組み込みの列挙型がないため、値を定義する定数を持つ各列挙型のモジュールを作成します。
次のような.protoファイルがある場合:
message Foo {
enum SomeEnum {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
optional SomeEnum bar = 1;
}
次のように列挙型を参照できます。
print Foo::SomeEnum::VALUE_A # => 0
message.bar = Foo::SomeEnum::VALUE_A
列挙型のフィールドには数字もしくはシンボルをセットすることができます。 値を読み戻すときに、列挙型の値が既知の場合はシンボルになり、未知の場合は数値になります。 proto3では列挙型が取りうる値の集合は制限されていないので、事前に定義されていなくても、列挙型フィールドに任意の数字を割り当てることができます。
message.bar = 0
puts message.bar.inspect # => :VALUE_A
message.bar = :VALUE_B
puts message.bar.inspect # => :VALUE_B
message.bar = 999
puts message.bar.inspect # => 999
# RangeError: 列挙型フィールドに対する未知のシンボル
message.bar = :UNDEFINED_VALUE
# 列挙型の値でswitchさせるのが便利です
case message.bar
when :VALUE_A
# ...
when :VALUE_B
# ...
when :VALUE_C
# ...
else
# ...
end
Enum
モジュールは、次のユーティリティメソッドも定義します。
- Enum#lookup(number): 数値からラベルを探して返します。もし存在しなければnilを返します。数値に
- 相当するラベルが複数ある場合、最初に定義されたラベルを返します。
- Enum#resolve(symbol): ラベルから数値を返します。存在しない場合はnilを返します。
- Enum#descriptor: この列挙型のdescrptorを返します。
oneof
次のようなoneofを持ったメッセージ型があるとします。
message Foo {
oneof test_oneof {
string name = 1;
int32 serial_number = 2;
}
}
Foo
に対応するRubyのクラスには、通常のフィールドと同じようなアクセサメソッドを持つname
とserial_number
というメンバー変数が含まれます。 ただし、通常のフィールドとは異なり、一度に1つまでしか、oneofのフィールドにには設定できません。そのため、あるフィールドに値を入れると他のフィールドはクリアされます。
message = Foo.new
# フィールドにはデフォルト値が入っている
raise unless message.name == ""
raise unless message.serial_number == 0
raise unless message.test_oneof == nil
message.name = "Bender"
raise unless message.name == "Bender"
raise unless message.serial_number == 0
raise unless message.test_oneof == :name
# serial_numberにセットするとnameがクリアされる
message.serial_number = 2716057
raise unless message.name == ""
raise unless message.test_oneof == :serial_number
# serial_numberにnilを入れるとoneofフィールドがクリアされる
message.serial_number = nil
raise unless message.test_oneof == nil