aboutsummaryrefslogtreecommitdiffhomepage
path: root/ruby
diff options
context:
space:
mode:
authorGravatar Chris Fallin <cfallin@google.com>2015-01-14 18:02:27 -0800
committerGravatar Chris Fallin <cfallin@google.com>2015-01-14 18:02:27 -0800
commite2debef5d8cd084946bd14fecabda5c328382114 (patch)
tree341b296a4b919ef37da1fb84b26c90e0bc9f753b /ruby
parente1b7d38d9ad5dfd3719b1e9b1d588e08aba1afe8 (diff)
Ruby extension: added oneof accessor.
Diffstat (limited to 'ruby')
-rw-r--r--ruby/ext/google/protobuf_c/encode_decode.c3
-rw-r--r--ruby/ext/google/protobuf_c/message.c45
-rw-r--r--ruby/tests/basic.rb6
3 files changed, 53 insertions, 1 deletions
diff --git a/ruby/ext/google/protobuf_c/encode_decode.c b/ruby/ext/google/protobuf_c/encode_decode.c
index 5e53b694..0db86209 100644
--- a/ruby/ext/google/protobuf_c/encode_decode.c
+++ b/ruby/ext/google/protobuf_c/encode_decode.c
@@ -77,7 +77,8 @@ static const void *newoneofhandlerdata(upb_handlers *h,
// we don't expose these numbers to the user, so the only requirement is that
// we have some unique ID for each union case/possibility. The field tag
// numbers are already present and are easy to use so there's no reason to
- // create a separate ID space.
+ // create a separate ID space. In addition, using the field tag number here
+ // lets us easily look up the field in the oneof accessor.
hd->oneof_case_num = upb_fielddef_number(f);
if (upb_fielddef_type(f) == UPB_TYPE_MESSAGE) {
hd->md = upb_fielddef_msgsubdef(f);
diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c
index ee8881d4..d0b09014 100644
--- a/ruby/ext/google/protobuf_c/message.c
+++ b/ruby/ext/google/protobuf_c/message.c
@@ -70,6 +70,36 @@ VALUE Message_alloc(VALUE klass) {
return ret;
}
+static VALUE which_oneof_field(MessageHeader* self, const upb_oneofdef* o) {
+ // If no fields in the oneof, always nil.
+ if (upb_oneofdef_numfields(o) == 0) {
+ return Qnil;
+ }
+ // Grab the first field in the oneof so we can get its layout info to find the
+ // oneof_case field.
+ upb_oneof_iter it;
+ upb_oneof_begin(&it, o);
+ assert(!upb_oneof_done(&it));
+ const upb_fielddef* first_field = upb_oneof_iter_field(&it);
+ assert(upb_fielddef_containingoneof(first_field) != NULL);
+
+ size_t case_ofs =
+ self->descriptor->layout->
+ fields[upb_fielddef_index(first_field)].case_offset;
+ uint32_t oneof_case = *((uint32_t*)(Message_data(self) + case_ofs));
+
+ // oneof_case == 0 indicates no field set.
+ if (oneof_case == 0) {
+ return Qnil;
+ }
+
+ // oneof_case is a field index, so find that field.
+ const upb_fielddef* f = upb_oneofdef_itof(o, oneof_case);
+ assert(f != NULL);
+
+ return ID2SYM(rb_intern(upb_fielddef_name(f)));
+}
+
/*
* call-seq:
* Message.method_missing(*args)
@@ -82,6 +112,10 @@ VALUE Message_alloc(VALUE klass) {
*
* msg.foo = 42
* puts msg.foo
+ *
+ * This method also provides read-only accessors for oneofs. If a oneof exists
+ * with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to
+ * the name of the field in that oneof that is currently set, or nil if none.
*/
VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
MessageHeader* self;
@@ -104,6 +138,17 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
name_len--;
}
+ // Check for a oneof name first.
+ const upb_oneofdef* o = upb_msgdef_ntoo(self->descriptor->msgdef,
+ name, name_len);
+ if (o != NULL) {
+ if (setter) {
+ rb_raise(rb_eRuntimeError, "Oneof accessors are read-only.");
+ }
+ return which_oneof_field(self, o);
+ }
+
+ // Otherwise, check for a field with that name.
const upb_fielddef* f = upb_msgdef_ntof(self->descriptor->msgdef,
name, name_len);
diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb
index 3e99f3b0..1b7508bb 100644
--- a/ruby/tests/basic.rb
+++ b/ruby/tests/basic.rb
@@ -615,30 +615,35 @@ module BasicTest
assert d.b == nil
assert d.c == nil
assert d.d == nil
+ assert d.my_oneof == nil
d.a = "hi"
assert d.a == "hi"
assert d.b == nil
assert d.c == nil
assert d.d == nil
+ assert d.my_oneof == :a
d.b = 42
assert d.a == nil
assert d.b == 42
assert d.c == nil
assert d.d == nil
+ assert d.my_oneof == :b
d.c = TestMessage2.new(:foo => 100)
assert d.a == nil
assert d.b == nil
assert d.c.foo == 100
assert d.d == nil
+ assert d.my_oneof == :c
d.d = :C
assert d.a == nil
assert d.b == nil
assert d.c == nil
assert d.d == :C
+ assert d.my_oneof == :d
d2 = OneofMessage.decode(OneofMessage.encode(d))
assert d2 == d
@@ -669,6 +674,7 @@ module BasicTest
d5.a = nil
assert d5.a == nil
assert OneofMessage.encode(d5) == ''
+ assert d5.my_oneof == nil
end
def test_enum_field