ruby: No way to access `oneof` field if it shares the name of a method on `Object`
If a oneof field shares the name of a Ruby method defined on Object or Kernel, there is no way to get the oneof field.
Normally, you do something like my_message.my_oneof to get the value. But for a proto definition like oneof method { ... }, that would mean doing my_message.method, which then attempts to call Kernel#method in the Ruby VM (e.g., to look up a method with the given name). That then fails, because method takes a required arg, but that's irrelevant: what it's not doing is looking up the oneof field named method on the message.
Normal, non-oneof fields work around this via the [] method on AbstractMessage, so that you can do my_message["method"]. That access pattern does not work when method is a oneof field instead of a normal field on the message.
Since protobuf allows oneof fields defined with arbitrary names (including names that collide with Ruby method names), there needs to be a way to access the field even in the presence of these collisions.
To my knowledge, there is no workaround for this in the mean time.
Update: https://github.com/protocolbuffers/protobuf/issues/21736#issuecomment-2881258582
What version of protobuf and what language are you using?
Language: Ruby
❯ bundle info google-protobuf
* google-protobuf (4.30.2)
Summary: Protocol Buffers
Homepage: https://developers.google.com/protocol-buffers
Source Code: https://github.com/protocolbuffers/protobuf/tree/v4.30.2/ruby
Path: /pay/home/jez/sandbox/vendor/bundle/ruby/3.3.0/gems/google-protobuf-4.30.2-x86_64-linux
❯ bin/protoc --version
libprotoc 31.0-rc2
What operating system (Linux, Windows, ...) and version?
Linux (platform agnostic)
What runtime / compiler are you using (e.g., python version or gcc version)
Ruby 3.3, but should be version agnostic
What did you do?
Gemfile
source 'https://rubygems.org'
gem 'google-protobuf'
Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
bigdecimal (3.1.9)
google-protobuf (4.30.2)
bigdecimal
rake (>= 13)
google-protobuf (4.30.2-aarch64-linux)
bigdecimal
rake (>= 13)
google-protobuf (4.30.2-arm64-darwin)
bigdecimal
rake (>= 13)
google-protobuf (4.30.2-x86-linux)
bigdecimal
rake (>= 13)
google-protobuf (4.30.2-x86_64-darwin)
bigdecimal
rake (>= 13)
google-protobuf (4.30.2-x86_64-linux)
bigdecimal
rake (>= 13)
rake (13.2.1)
PLATFORMS
aarch64-linux
arm64-darwin
ruby
x86-linux
x86_64-darwin
x86_64-linux
DEPENDENCIES
google-protobuf
BUNDLED WITH
2.5.22
test_message.proto
message TestMessageEither {
oneof either {
string left = 1;
int32 right = 2;
}
}
message TestMessageMethod {
oneof method {
string left = 1;
int32 right = 2;
}
}
test_message_pb.rb
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test_message.proto
require 'google/protobuf'
descriptor_data = "\n\x12test_message.proto\">\n\x11TestMessageEither\x12\x0e\n\x04left\x18\x01 \x01(\tH\x00\x12\x0f\n\x05right\x18\x02 \x01(\x05H\x00\x42\x08\n\x06\x65ither\">\n\x11TestMessageMethod\x12\x0e\n\x04left\x18\x01 \x01(\tH\x00\x12\x0f\n\x05right\x18\x02 \x01(\x05H\x00\x42\x08\n\x06method"
pool = ::Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
TestMessageEither = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("TestMessageEither").msgclass
TestMessageMethod = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("TestMessageMethod").msgclass
foo.rb
require 'google/protobuf'
require_relative './test_message_pb.rb'
def try(testcase)
print(testcase)
print(": ")
begin
yield
rescue => e
STDERR.puts("#{e.class}: #{e.message}")
STDERR.puts(e.backtrace)
end
puts
end
puts "----- Expected behavior ------------------------------------------------"
msg = TestMessageEither.new(left: "hello")
try("msg") { p(msg) }
try("msg.left") { p(msg.left) }
try('msg["left"]') { p(msg["left"]) }
try("msg.either") { p(msg.either) }
try('msg["either"]') { p(msg["either"]) }
puts "----- Unexpected behavior ----------------------------------------------"
msg = TestMessageMethod.new(left: "hello")
try("msg") { p(msg) }
try("msg.left") { p(msg.left) }
try('msg["left"]') { p(msg["left"]) }
try("msg.method") { p(msg.method) }
try('msg["method"]') { p(msg["method"]) }
.ruby-version
3.3.5
bundle exec ruby foo.rb
What did you expect to see
No exception for the msg.method line, or at least something useful for the msg["method"] line.
What did you see instead?
❯ bundle exec ruby foo.rb
----- Expected behavior ------------------------------------------------
msg: <TestMessageEither: left: "hello">
msg.left: "hello"
msg["left"]: "hello"
msg.either: :left
msg["either"]: nil
----- Unexpected behavior ----------------------------------------------
msg: <TestMessageMethod: left: "hello">
msg.left: "hello"
msg["left"]: "hello"
msg.method: ArgumentError: wrong number of arguments (given 0, expected 1)
foo.rb:29:in `method'
foo.rb:29:in `block in <main>'
foo.rb:8:in `try'
foo.rb:29:in `<main>'
msg["method"]: nil
Anything else we should know about your project / environment
I have included a zipfile if you'd like to avoid copy/pasting a bunch of files:
unzip test.zip
cd sandbox
bundle install
bundle exec ruby foo.rb
Update: I was able to find a very gross workaround by calling method_missing directly:
message.method_missing(:method)
Glad to know a workaround exists, but I agree this is not the intended behavior. What the intended behavior is might not be well specified yet, so give me a little time to research how we handle this edge case in other language bindings.
We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please add a comment.
This issue is labeled inactive because the last activity was over 90 days ago. This issue will be closed and archived after 14 additional days without activity.
This is still relevant. The [] method on a message likely needs to change to accept oneof fields.
We probably won't be able to get to this in the near term, but would accept a patch.
We probably won't be able to get to this in the near term, but would accept a patch.
Sounds good, here you go: #24181
Github actions closed the pr for unknown reasons, we reopened it and are waiting for a response.