How to Use Protocol Buffers in Ruby

A practical guide to working with Protocol Buffers in Ruby and Rails applications

Published: January 2025 • 9 min read

Protocol Buffers works smoothly with Ruby and is perfect for Rails APIs, microservices, and background jobs. While Ruby developers often use JSON, Protobuf offers faster serialization and smaller message sizes, especially useful when building mobile backends or high-performance services.

This guide shows you how to get started with Protocol Buffers in Ruby. We'll use simple examples and keep things practical. Ruby's clean syntax makes working with protobuf straightforward and enjoyable.

What You'll Need

  • Ruby: Version 2.7 or higher (check with ruby -v)
  • Bundler: Ruby gem manager (usually comes with Ruby)
  • Protocol Buffer Compiler: We'll install this next

Step 1: Install Protocol Buffers

First, install the protobuf compiler (protoc). On macOS:

brew install protobuf

On Ubuntu/Debian:

sudo apt update
sudo apt install protobuf-compiler

# Verify installation
protoc --version

Next, add the Ruby protobuf gem to your Gemfile:

# Gemfile
gem 'google-protobuf'

Install it:

bundle install

Step 2: Set Up Your Project

Create a simple Ruby project structure:

mkdir ruby-protobuf-demo
cd ruby-protobuf-demo
bundle init
bundle add google-protobuf

mkdir protos
mkdir lib/generated -p

This creates a basic Ruby project with folders for proto files and generated code.

Step 3: Create Your .proto File

Create protos/subscriber.proto with a telecom subscriber example:

syntax = "proto3";

package telecom;

// Mobile subscriber information
message Subscriber {
  string msisdn = 1;           // Mobile number
  string name = 2;             // Subscriber name
  string email = 3;            // Email address
  SubscriptionType type = 4;   // Plan type
  bool active = 5;             // Account status
  repeated string services = 6; // Active services
}

enum SubscriptionType {
  PREPAID = 0;
  POSTPAID = 1;
  CORPORATE = 2;
}

Step 4: Compile the .proto File

Generate Ruby code from your proto file:

protoc --ruby_out=lib/generated --proto_path=protos protos/subscriber.proto

This creates lib/generated/subscriber_pb.rb with the Ruby classes.

Step 5: Use Protocol Buffers in Ruby

Create example.rb:

require 'google/protobuf'
require_relative 'lib/generated/subscriber_pb'

# Create a new subscriber
subscriber = Telecom::Subscriber.new(
  msisdn: '+91-9876543210',
  name: 'Telecom Customer',
  email: 'customer@telecom.com',
  type: :POSTPAID,
  active: true,
  services: ['Voice', 'Data', '4G LTE']
)

puts "Created subscriber: #{subscriber.name}"
puts "MSISDN: #{subscriber.msisdn}"
puts "Type: #{subscriber.type}"

# Serialize to binary
binary_data = Telecom::Subscriber.encode(subscriber)
puts "Serialized to #{binary_data.bytesize} bytes"

# Deserialize from binary
decoded = Telecom::Subscriber.decode(binary_data)
puts "\nDeserialized subscriber: #{decoded.name}"
puts "Email: #{decoded.email}"
puts "Active: #{decoded.active}"

# Access repeated fields
puts "\nActive services:"
decoded.services.each do |service|
  puts "  - #{service}"
end

Step 6: Run Your Application

Run the Ruby script:

ruby example.rb

You should see output like:

Created subscriber: Telecom Customer
MSISDN: +91-9876543210
Type: POSTPAID
Serialized to 68 bytes

Deserialized subscriber: Telecom Customer
Email: customer@telecom.com
Active: true

Active services:
  - Voice
  - Data
  - 4G LTE

Saving and Loading from Files

Save and load protobuf data:

require 'google/protobuf'
require_relative 'lib/generated/subscriber_pb'

# Create subscriber
subscriber = Telecom::Subscriber.new(
  msisdn: '+91-9123456789',
  name: 'Network Admin',
  email: 'admin@telecom.com',
  type: :CORPORATE,
  active: true
)

# Save to file
File.open('subscriber.bin', 'wb') do |file|
  file.write(Telecom::Subscriber.encode(subscriber))
end
puts "Saved to file"

# Load from file
binary_data = File.read('subscriber.bin')
loaded = Telecom::Subscriber.decode(binary_data)
puts "Loaded: #{loaded.name}"
puts "MSISDN: #{loaded.msisdn}"

Convert Between JSON and Protobuf

Ruby protobuf supports JSON encoding and decoding:

require 'google/protobuf'
require_relative 'lib/generated/subscriber_pb'

# Create subscriber
subscriber = Telecom::Subscriber.new(
  msisdn: '+91-9876543210',
  name: 'Mobile User',
  type: :PREPAID,
  active: true
)

# Convert to JSON
json_string = Telecom::Subscriber.encode_json(subscriber)
puts "JSON output:"
puts json_string

# Parse from JSON
json_data = '{
  "msisdn": "+91-9111111111",
  "name": "New Subscriber",
  "type": "POSTPAID",
  "active": true
}'

new_subscriber = Telecom::Subscriber.decode_json(json_data)
puts "\nParsed from JSON: #{new_subscriber.name}"
puts "Type: #{new_subscriber.type}"

Using Protobuf with Rails

Here's how to use Protobuf in a Rails API controller:

# app/controllers/subscribers_controller.rb
class SubscribersController < ApplicationController
  require_relative '../../lib/generated/subscriber_pb'
  
  def create
    # Accept JSON, convert to Protobuf
    subscriber = Telecom::Subscriber.new(
      msisdn: params[:msisdn],
      name: params[:name],
      email: params[:email],
      type: :POSTPAID,
      active: true
    )
    
    # Save to database or process...
    
    # Return protobuf binary
    render body: Telecom::Subscriber.encode(subscriber),
           content_type: 'application/x-protobuf'
  end
  
  def show
    # Fetch subscriber from database
    subscriber = Telecom::Subscriber.new(
      msisdn: params[:id],
      name: 'Retrieved Subscriber',
      type: :PREPAID,
      active: true
    )
    
    # Return as protobuf
    render body: Telecom::Subscriber.encode(subscriber),
           content_type: 'application/x-protobuf'
  end
  
  def parse
    # Receive protobuf binary in request
    binary_data = request.body.read
    subscriber = Telecom::Subscriber.decode(binary_data)
    
    # Process the subscriber
    render json: {
      name: subscriber.name,
      msisdn: subscriber.msisdn,
      type: subscriber.type.to_s
    }
  end
end

Best Practices for Ruby

Use Symbols for Enums

In Ruby, protobuf enums work as symbols. Use :POSTPAID instead of strings for enum values.

Handle Binary Data Properly

When reading/writing files, always use binary mode ('wb' or'rb') to avoid encoding issues.

Keep Generated Files Separate

Store generated Ruby files in a dedicated directory and never edit them manually. They'll be overwritten on recompilation.

Use Class Methods

Use encode anddecode class methods for cleaner serialization code.

Common Issues

Issue: Cannot load generated file

Solution: Make sure you're using require_relative with the correct path to your generated _pb.rb file.

Issue: Encoding errors with binary data

Solution: Always use binary mode when reading/writing protobuf files. Use File.open('file.bin', 'wb') for writing.

Issue: protoc command not found

Solution: Install the Protocol Buffer compiler using Homebrew on macOS (brew install protobuf) or apt on Ubuntu.

Related Tools

Additional Resources

Official Documentation & References

Conclusion

Protocol Buffers fits naturally into Ruby's ecosystem. The Ruby gem provides a clean, idiomatic API that feels right at home in Ruby code. Whether you're building Rails APIs, Sinatra microservices, or background workers, protobuf offers solid performance benefits.

Start with these simple examples and gradually integrate protobuf into your Ruby applications. You'll notice the performance improvements especially when dealing with mobile clients or high-throughput services.