feat(6178): emit ruby enum as integer (#11673)

Fixes #6178

- Add a new option `enums_as_integers` to emit enum as integer value.

Closes #11673

COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/11673 from MQuy:feat/6178-ruby-enum-as-integer 90f986a5fd2f5e8bda3f338c67304ef96feee446
PiperOrigin-RevId: 514789180
diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c
index 067a446..ab19d27 100644
--- a/ruby/ext/google/protobuf_c/message.c
+++ b/ruby/ext/google/protobuf_c/message.c
@@ -1164,6 +1164,12 @@
                               Qfalse))) {
       options |= upb_JsonEncode_EmitDefaults;
     }
+
+    if (RTEST(rb_hash_lookup2(hash_args,
+                              ID2SYM(rb_intern("format_enums_as_integers")),
+                              Qfalse))) {
+      options |= upb_JsonEncode_FormatEnumsAsIntegers;
+    }
   }
 
   upb_Status_Clear(&status);
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
index 92a31d6..dc76e2d 100644
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
@@ -669,6 +669,7 @@
    * @param options [Hash] options for the decoder
    *  preserve_proto_fieldnames: set true to use original fieldnames (default is to camelCase)
    *  emit_defaults: set true to emit 0/false values (default is to omit them)
+   *  format_enums_as_integers: set true to emit enum values as integer (default is string)
    */
   @JRubyMethod(name = "encode_json", required = 1, optional = 1, meta = true)
   public static IRubyObject encodeJson(
@@ -690,6 +691,8 @@
 
       IRubyObject emitDefaults = options.fastARef(runtime.newSymbol("emit_defaults"));
       IRubyObject preserveNames = options.fastARef(runtime.newSymbol("preserve_proto_fieldnames"));
+      IRubyObject printingEnumsAsInts =
+          options.fastARef(runtime.newSymbol("format_enums_as_integers"));
 
       if (emitDefaults != null && emitDefaults.isTrue()) {
         printer = printer.includingDefaultValueFields();
@@ -698,6 +701,10 @@
       if (preserveNames != null && preserveNames.isTrue()) {
         printer = printer.preservingProtoFieldNames();
       }
+
+      if (printingEnumsAsInts != null && printingEnumsAsInts.isTrue()) {
+        printer = printer.printingEnumsAsInts();
+      }
     }
     printer =
         printer.usingTypeRegistry(
diff --git a/ruby/tests/encode_decode_test.rb b/ruby/tests/encode_decode_test.rb
index df2cd36..710e754 100755
--- a/ruby/tests/encode_decode_test.rb
+++ b/ruby/tests/encode_decode_test.rb
@@ -84,6 +84,34 @@
     )
 
     assert_match 'optional_int32', json
+
+
+    # Test for enums printing as ints.
+    msg = A::B::C::TestMessage.new({ optional_enum: 1 })
+    json = A::B::C::TestMessage.encode_json(
+      msg, 
+      { :format_enums_as_integers => true }
+    )
+
+    assert_match '"optionalEnum":1', json
+
+    # Test for default enum being printed as int.
+    msg = A::B::C::TestMessage.new({ optional_enum: 0 })
+    json = A::B::C::TestMessage.encode_json(
+      msg, 
+      { :format_enums_as_integers => true, :emit_defaults => true }
+    )
+
+    assert_match '"optionalEnum":0', json
+
+    # Test for repeated enums printing as ints.
+    msg = A::B::C::TestMessage.new({ repeated_enum: [0,1,2,3] })
+    json = A::B::C::TestMessage.encode_json(
+      msg, 
+      { :format_enums_as_integers => true }
+    )
+
+    assert_match '"repeatedEnum":[0,1,2,3]', json
   end
 
   def test_encode_wrong_msg