Ruby 生成コードガイド
このドキュメントを読む前に、proto2 または proto3 の言語ガイドを読むことをお勧めします。
Ruby 用のプロトコルコンパイラは、メッセージスキーマを定義するために DSL を使用する Ruby ソースファイルを生成します。ただし、DSL はまだ変更される可能性があります。このガイドでは、生成されたメッセージの API のみについて説明し、DSL については説明しません。
コンパイラの起動
プロトコルバッファコンパイラは、`--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` ファイルを読み取り、2 つの出力ファイル (`build/gen/foo_pb.rb` および `build/gen/bar/baz_pb.rb`) を生成します。コンパイラは必要に応じて `build/gen/bar` ディレクトリを自動的に作成しますが、`build` または `build/gen` は作成しません。これらは事前に存在している必要があります。
パッケージ
`.proto` ファイルで定義されたパッケージ名は、生成されたメッセージのモジュール構造を生成するために使用されます。次のようなファイルが与えられた場合
package foo_bar.baz;
message MyMessage {}
プロトコルコンパイラは、`FooBar::Baz::MyMessage` という名前の出力メッセージを生成します。
ただし、`.proto` ファイルに次のように `ruby_package` オプションが含まれている場合
option ruby_package = "Foo::Bar";
生成された出力は、代わりに `ruby_package` オプションを優先し、`Foo::Bar::MyMessage` を生成します。
メッセージ
簡単なメッセージ宣言が与えられた場合
message Foo {}
プロトコルバッファコンパイラは、`Foo` という名前のクラスを生成します。生成されたクラスは Ruby の `Object` クラスから派生します (proto には共通の基底クラスはありません)。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)
: このメッセージのバイナリ protobuf をデコードし、新しいインスタンスで返します。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` として参照できます。
フィールド
メッセージ型の各フィールドには、フィールドを設定および取得するためのアクセサメソッドがあります。したがって、フィールド `foo` が与えられた場合、次のように記述できます。
message.foo = get_value()
print message.foo
フィールドを設定するたびに、値はフィールドの宣言された型に対して型チェックされます。値が間違った型 (または範囲外) の場合、例外が発生します。
単数フィールド
単数プリミティブフィールド (数値、文字列、およびブール値) の場合、フィールドに割り当てる値は正しい型である必要があり、適切な範囲内にある必要があります。
- 数値型: 値は `Fixnum`、`Bignum`、または `Float` である必要があります。割り当てる値は、ターゲット型で正確に表現できる必要があります。したがって、`1.0` を int32 フィールドに割り当てるのは問題ありませんが、`1.2` を割り当てるのは問題ありません。
- ブール値フィールド: 値は `true` または `false` である必要があります。他の値は暗黙的に true/false に変換されません。
- Bytes フィールド: 割り当てられた値は `String` オブジェクトである必要があります。protobuf ライブラリは文字列を複製し、ASCII-8BIT エンコーディングに変換して、それをフリーズします。
- String フィールド: 割り当てられた値は `String` オブジェクトである必要があります。protobuf ライブラリは文字列を複製し、UTF-8 エンコーディングに変換して、それをフリーズします。
自動変換を実行するために、自動的な `#to_s`、`#to_i` などの呼び出しは行われません。必要に応じて、最初に自分で値を変換する必要があります。
存在の確認
`optional` フィールドを使用する場合、フィールドの存在は、生成された `has_...?` メソッドを呼び出すことによって確認されます。任意の値 (デフォルト値でも) を設定すると、フィールドが存在するものとしてマークされます。フィールドは、別の生成された `clear_...` メソッドを呼び出すことによってクリアできます。たとえば、int32 フィールド `foo` を持つメッセージ `MyMessage` の場合
m = MyMessage.new
raise unless !m.has_foo?
m.foo = 0
raise unless m.has_foo?
m.clear_foo
raise unless !m.has_foo?
単数メッセージフィールド
サブメッセージの場合、設定されていないフィールドは `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?
raise unless message.submessage_field == nil
puts "Submessage field is unset."
else
raise unless message.submessage_field != nil
message.clear_submessage_field
raise unless message.submessage_field == nil
puts "Cleared submessage field."
end
サブメッセージを割り当てる場合、それは正しい型の生成されたメッセージオブジェクトである必要があります。
サブメッセージを割り当てるときに、メッセージサイクルを作成することが可能です。例えば
// foo.proto
message RecursiveMessage {
RecursiveMessage submessage = 1;
}
# test.rb
require 'foo'
message = RecursiveSubmessage.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?
`RepeatedField` 型は、通常の Ruby `Array` と同じすべてのメソッドをサポートしています。`repeated_field.to_a` を使用して通常の Ruby 配列に変換できます。
単数フィールドとは異なり、`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 にはネイティブな列挙型がないため、値を定義する定数を持つ列挙型ごとにモジュールを作成します。`.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
# 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?