Rubyが生成したコードガイド
このドキュメントを読む前に、proto2、proto3、またはエディションの言語ガイドを読む必要があります。
コンパイラの呼び出し
Protocol Bufferコンパイラは、`--ruby_out=` コマンドラインフラグを指定して呼び出されると、Ruby出力を生成します。`--ruby_out=` オプションのパラメータは、コンパイラがRuby出力を書き込むディレクトリです。コンパイラは、入力された各 `.proto` ファイルに対して `.rb` ファイルを作成します。出力ファイルの名前は、`.proto` ファイルの名前を取得し、2つの変更を加えることで計算されます。
- 拡張子 (`.proto`) は `_pb.rb` に置き換えられます。
- プロトパス (`--proto_path=` または `-I` コマンドラインフラグで指定) は、出力パス (`--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 {}
Protocolコンパイラは、`FooBar::Baz::MyMessage` という名前の出力メッセージを生成します。
ただし、`.proto` ファイルに次のような `ruby_package` オプションが含まれている場合:
option ruby_package = "Foo::Bar";
生成される出力は `ruby_package` オプションを優先し、`Foo::Bar::MyMessage` を生成します。
メッセージ
単純なメッセージ宣言を考えます。
message Foo {}
Protocol Bufferコンパイラは、`Foo` というクラスを生成します。生成されたクラスはRubyの `Object` クラスから派生します(プロトには共通の基底クラスはありません)。C++やJavaとは異なり、Rubyで生成されたコードは、`.proto` ファイルの `optimize_for` オプションの影響を受けません。実際、すべてのRubyコードはコードサイズのために最適化されています。
独自の `Foo` サブクラスを作成するべきではありません。生成されたクラスはサブクラス化を想定して設計されておらず、「脆い基底クラス」の問題につながる可能性があります。
Rubyのメッセージクラスは、各フィールドのアクセサを定義し、以下の標準メソッドも提供します。
Message#dup、Message#clone: このメッセージのシャローコピーを実行し、新しいコピーを返します。Message#==: 2つのメッセージ間でディープな等価比較を実行します。Message#hash: メッセージの値のシャローハッシュを計算します。Message#to_hash、Message#to_h: オブジェクトをRubyの `Hash` オブジェクトに変換します。トップレベルのメッセージのみが変換されます。Message#inspect: このメッセージを表す人間が読める形式の文字列を返します。Message#[]、Message#[]=: 文字列名でフィールドを取得または設定します。将来的には、拡張機能の取得/設定にも使用される可能性があります。
メッセージクラスは、以下のメソッドも静的メソッドとして定義します。(一般的に、通常のメソッドは.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: MyMessage::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` として参照できます。
フィールド
メッセージ型内の各フィールドについて、フィールドを設定および取得するためのアクセサメソッドがあります。したがって、`foo` というフィールドがある場合、次のように記述できます。
message.foo = get_value()
print message.foo
フィールドを設定するたびに、その値は宣言されたフィールドの型に対して型チェックされます。値が間違った型である(または範囲外である)場合、例外がスローされます。
単数フィールド
単数形のプリミティブフィールド(数値、文字列、ブール値)の場合、フィールドに割り当てる値は正しい型であり、適切な範囲内である必要があります。
- 数値型: 値は `Fixnum`、`Bignum`、または `Float` である必要があります。割り当てる値は、ターゲット型で正確に表現できる必要があります。したがって、int32フィールドに `1.0` を割り当てるのは問題ありませんが、`1.2` を割り当てることはできません。
- ブーリアンフィールド: 値は `true` または `false` でなければなりません。他の値は暗黙的にtrue/falseに変換されません。
- Bytesフィールド: 割り当てられた値は `String` オブジェクトでなければなりません。protobufライブラリは文字列を複製し、ASCII-8BITエンコーディングに変換し、フリーズします。
- Stringフィールド: 割り当てられた値は `String` オブジェクトでなければなりません。protobufライブラリは文字列を複製し、UTF-8エンコーディングに変換し、フリーズします。
自動変換を実行するために、自動的な `to_s`、`to_i` などの呼び出しは行われません。必要に応じて、まず値を自分で変換する必要があります。
存在の確認
明示的なフィールドの存在は、`field_presence` フィーチャー (エディションの場合)、`optional` キーワード (proto2/proto3の場合)、およびフィールド型 (メッセージおよびoneofフィールドは常に明示的な存在を持つ) によって決定されます。フィールドに存在がある場合、生成された `has_...?` メソッドを呼び出すことで、そのフィールドがメッセージに設定されているかどうかを確認できます。デフォルト値であっても、何らかの値を設定すると、フィールドは存在するとしてマークされます。フィールドは、別の生成された `clear_...` メソッドを呼び出すことでクリアできます。
たとえば、int32フィールド `foo` を持つメッセージ `MyMessage` の場合、
message MyMessage {
int32 foo = 1;
}
foo の存在は次のように確認できます。
m = MyMessage.new
raise if m.has_foo?
m.foo = 0
raise unless m.has_foo?
m.clear_foo
raise if m.has_foo?
単数メッセージフィールド
サブメッセージフィールドは、`optional` とマークされているかどうかにかかわらず、常に存在します。設定されていないサブメッセージフィールドは `nil` を返すため、メッセージが明示的に設定されたかどうかを常に判断できます。サブメッセージフィールドをクリアするには、その値を明示的に `nil` に設定します。
if message.submessage_field.nil?
puts "Submessage field is unset."
else
message.submessage_field = nil
puts "Cleared submessage field."
end
nil との比較と代入に加えて、生成されたメッセージには `has_...` と `clear_...` メソッドがあり、これらは基本型と同様に動作します。
if !message.has_submessage_field?
puts "Submessage field is unset."
else
message.clear_submessage_field
raise if message.has_submessage_field?
puts "Cleared submessage field."
end
サブメッセージを割り当てる場合、それは正しい型の生成されたメッセージオブジェクトでなければなりません。
サブメッセージを割り当てるときに、メッセージの循環を作成することが可能です。たとえば、
// foo.proto
message RecursiveMessage {
RecursiveMessage submessage = 1;
}
# test.rb
require 'foo'
message = RecursiveMessage.new
message.submessage = message
これをシリアル化しようとすると、ライブラリはサイクルを検出し、シリアル化に失敗します。
繰り返しフィールド
繰り返しフィールドは、カスタムクラス `Google::Protobuf::RepeatedField` を使用して表現されます。このクラスはRubyの `Array` のように動作し、`Enumerable` をミックスインします。通常のRubyの配列とは異なり、`RepeatedField` は特定の型で構築され、配列のすべてのメンバーが正しい型であることを期待します。型と範囲は、メッセージフィールドと同様にチェックされます。
int_repeatedfield = Google::Protobuf::RepeatedField.new(:int32, [1, 2, 3])
raise unless !int_repeatedfield.empty?
# Raises TypeError.
int_repeatedfield[2] = "not an int32"
# Raises RangeError
int_repeatedfield[2] = 2**33
message.int32_repeated_field = int_repeatedfield
# This isn't allowed; the regular Ruby array doesn't enforce types like we need.
message.int32_repeated_field = [1, 2, 3, 4]
# This is fine, since the elements are copied into the type-safe array.
message.int32_repeated_field += [1, 2, 3, 4]
# The elements can be cleared without reassigning.
int_repeatedfield.clear
raise unless int_repeatedfield.empty?
メッセージを含む繰り返しフィールドの場合、Google::Protobuf::RepeatedField のコンストラクタは、:message、サブメッセージのクラス、および設定する値の3つの引数をとるバリアントをサポートします。
first_message = MySubMessage.new(foo: 42)
second_message = MySubMessage.new(foo: 79)
repeated_field = Google::Protobuf::RepeatedField.new(
:message,
MySubMessage,
[first_message, second_message]
)
message.sub_message_repeated_field = repeated_field
RepeatedField型は、通常のRuby Arrayと同じメソッドをすべてサポートしています。repeated_field.to_a を使用して、通常のRuby Arrayに変換できます。
単一フィールドとは異なり、繰り返しフィールドに対しては `has_...?` メソッドは決して生成されません。
マップフィールド
マップフィールドは、Rubyの `Hash` (Google::Protobuf::Map) のように動作する特殊なクラスを使用して表現されます。通常のRubyハッシュとは異なり、`Map` はキーと値に対して特定の型で構築され、マップのすべてのキーと値が正しい型であることを期待します。型と範囲は、メッセージフィールドと RepeatedField 要素と同様にチェックされます。
int_string_map = Google::Protobuf::Map.new(:int32, :string)
# Returns nil; items is not in the map.
print int_string_map[5]
# Raises TypeError, value should be a string
int_string_map[11] = 200
# Ok.
int_string_map[123] = "abc"
message.int32_string_map_field = int_string_map
列挙型
Rubyにはネイティブのenumがないため、値の定義には定数を持つ各enumのモジュールを作成します。`.proto` ファイルが次の場合:
message Foo {
enum SomeEnum {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
SomeEnum bar = 1;
}
enumの値は次のように参照できます。
print Foo::SomeEnum::VALUE_A # => 0
message.bar = Foo::SomeEnum::VALUE_A
enumフィールドには、数値またはシンボルを割り当てることができます。値を読み戻すとき、enum値が既知の場合はシンボルとなり、そうでない場合は数値となります。
proto3が使用する `OPEN` 列挙型では、その値が列挙型で定義されていなくても、任意の整数値を列挙型に割り当てることができます。
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
# Raises: RangeError: Unknown symbol value for enum field.
message.bar = :UNDEFINED_VALUE
# Switching on an enum value is convenient.
case message.bar
when :VALUE_A
# ...
when :VALUE_B
# ...
when :VALUE_C
# ...
else
# ...
end
列挙モジュールは、以下のユーティリティメソッドも定義します。
Foo::SomeEnum.lookup(number): 指定された番号を検索し、その名前を返します。見つからない場合は `nil` を返します。複数の名前がこの番号を持つ場合、最初に定義された名前を返します。Foo::SomeEnum.resolve(symbol): この列挙名の番号を返します。見つからない場合は `nil` を返します。Foo::SomeEnum.descriptor: この列挙型のディスクリプタを返します。
Oneof
oneofを持つメッセージの場合
message Foo {
oneof test_oneof {
string name = 1;
int32 serial_number = 2;
}
}
Foo に対応するRubyクラスには、通常のフィールドと同様にアクセサメソッドを持つ `name` と `serial_number` というメンバーがあります。ただし、通常のフィールドとは異なり、oneofのフィールドは同時に最大1つしか設定できません。そのため、1つのフィールドを設定すると、他のフィールドはクリアされます。
message = Foo.new
# Fields have their defaults.
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
# Setting serial_number clears name.
message.serial_number = 2716057
raise unless message.name == ""
raise unless message.test_oneof == :serial_number
# Setting serial_number to nil clears the oneof.
message.serial_number = nil
raise unless message.test_oneof == nil
proto2メッセージの場合、oneofメンバーも個別の `has_...?` メソッドを持ちます。
message = Foo.new
raise unless !message.has_test_oneof?
raise unless !message.has_name?
raise unless !message.has_serial_number?
raise unless !message.has_test_oneof?
message.name = "Bender"
raise unless message.has_test_oneof?
raise unless message.has_name?
raise unless !message.has_serial_number?
raise unless !message.has_test_oneof?