Skip to content

Ruby SDK Reference

TL;DR: Run gem install featureflip, call client.bool_variation("flag-key", { user_id: "u-123" }, false), and you’re evaluating flags — zero runtime dependencies.

The featureflip Ruby gem provides a thread-safe client for evaluating feature flags in any Ruby 3.2+ application — Rails, Sinatra, Hanami, Sidekiq workers, Rake tasks, or plain scripts. The SDK has zero runtime gem dependencies; it uses the standard library’s net/http for HTTPS and SSE streaming, so it doesn’t fight with your existing HTTP gem stack and won’t pull in faraday or httparty transitively. Distribution is RubyGems under the featureflip name.

The client is intended as a long-lived process-level singleton. In Rails, instantiate it once in an initializer and store it on a class constant or Rails.configuration; in Sidekiq, share it across workers; in Puma, build it after fork. Variation methods read from a Concurrent::Hash and return synchronously, so you can call them from controllers, background jobs, mailers, or partials without thread-safety concerns. The streaming connection runs on a background Thread, which gracefully handles GVL contention because the hot path is simple I/O and a hash lookup.

Evaluation context is a plain Ruby Hash with a required user_id key — { user_id: "u-123", plan: "pro", country: "US" } is the entire API. The user_id anchors percentage rollouts so a given user lands in a stable bucket across processes, dynos, and pods. Custom keys feed targeting rules. Symbol and string keys both work — the SDK normalizes them internally — so you don’t have to fight the hash-keys-as-strings-or-symbols ambiguity that plagues most Ruby APIs.

View source on GitHub

Add to your Gemfile:

gem "featureflip"

Or install directly:

Terminal window
gem install featureflip

The gem has zero runtime dependencies — it uses only Ruby stdlib (net/http, digest/md5).

The Config class accepts the following options:

OptionTypeDefaultDescription
base_urlString"https://eval.featureflip.io"Base URL of the Evaluation API
connect_timeoutNumeric5Connection timeout in seconds
read_timeoutNumeric10Read timeout in seconds
streamingBooleantrueEnable SSE for real-time flag updates
poll_intervalNumeric30Polling interval in seconds (used when streaming is disabled)
send_eventsBooleantrueEnable analytics event tracking
flush_intervalNumeric30Interval in seconds between event flush batches
flush_batch_sizeInteger100Maximum number of events per flush batch
init_timeoutNumeric10Maximum time in seconds to wait for initialization
max_stream_retriesInteger5SSE retries before falling back to polling
config = Featureflip::Config.new(
base_url: "https://eval.featureflip.io",
streaming: true,
poll_interval: 60,
flush_interval: 15,
init_timeout: 30,
)

All timeout and interval values must be positive. The constructor validates these constraints and raises Featureflip::ConfigurationError for invalid values.

Create a client by providing an SDK key and optional configuration:

client = Featureflip::Client.new(
sdk_key: "sdk-dev-abc123",
config: Featureflip::Config.new(base_url: "https://eval.featureflip.io"),
)

The constructor performs initialization synchronously: it fetches the initial flag configuration, starts streaming or polling, and begins the event processor. If initialization fails or times out, an InitializationError is raised.

For applications that use a single client instance, the Featureflip module provides a convenience wrapper:

Featureflip.configure do |c|
c.sdk_key = "sdk-dev-abc123"
c.base_url = "https://eval.featureflip.io"
c.streaming = true
end
enabled = Featureflip.bool_variation("dark-mode", { user_id: "u-123" }, false)
Featureflip.close

The singleton is thread-safe — configure and close are protected by a Mutex.

If sdk_key is not provided, the client falls back to the FEATUREFLIP_SDK_KEY environment variable. A ConfigurationError is raised if neither is available.

ENV["FEATUREFLIP_SDK_KEY"] = "sdk-dev-abc123"
# SDK key read from environment
client = Featureflip::Client.new
client.initialized? # => true

Returns true once the client has successfully loaded flag data.

The Ruby SDK provides separate typed methods for each flag type. Context can use string or symbol keys — symbols are automatically converted to strings.

enabled = client.bool_variation("dark-mode", { user_id: "u-123" }, false)
tier = client.string_variation("pricing-tier", { user_id: "u-123" }, "free")
limit = client.number_variation("rate-limit", { user_id: "u-123" }, 100)
config = client.json_variation("ui-config", { user_id: "u-123" }, { "theme" => "light" })

Signature (same for all typed methods):

def bool_variation(key, context, default_value)
ParameterTypeDescription
keyStringThe flag key to evaluate
contextHashUser attributes for targeting. The user_id key is used for percentage rollouts
default_valueObjectValue returned if the flag is not found or evaluation fails

These methods never raise exceptions. On any error, the default value is returned.

Returns an EvaluationDetail with the evaluated value and metadata about the evaluation decision.

detail = client.variation_detail("dark-mode", { user_id: "u-123" }, false)
detail.value # true
detail.reason # "RuleMatch"
detail.rule_id # "rule-abc"
detail.variation_key # "true"

Signature:

def variation_detail(key, context, default_value)

A Struct containing the evaluation result:

PropertyTypeDescription
valueObjectThe evaluated flag value
reasonStringWhy this value was returned
rule_idString or nilThe ID of the matched targeting rule, if applicable
variation_keyString or nilThe key of the matched variation
ValueDescription
"Fallthrough"No rules matched; the fallthrough variation was served
"RuleMatch"A targeting rule matched the context
"FlagDisabled"The flag is disabled; the off variation was served
"FlagNotFound"The flag key does not exist
"Error"An error occurred during evaluation

Records a custom analytics event.

client.track("purchase-completed", { user_id: "u-123" }, {
plan: "pro",
amount: 29.99,
})

Signature:

def track(event_key, context, metadata = nil)

Sends user attributes for segment building and analytics.

client.identify({
user_id: "u-123",
plan: "pro",
})

Signature:

def identify(context)

Shuts down the client, stops streaming or polling, and flushes remaining events.

client.close

Can be called multiple times safely.

Forces all buffered events to be sent to the server immediately.

client.flush

Respawns background threads after a fork. Required for Puma, Unicorn, and Passenger.

client.restart

Ruby web servers that use forking (Puma, Unicorn, Passenger) kill background threads in worker processes. Call restart after fork to respawn streaming/polling threads:

config/puma.rb
on_worker_boot do
Featureflip.restart
end
# config/unicorn.rb
after_fork do |server, worker|
Featureflip.restart
end

Creates a test client with fixed flag values. No network calls are made and no background threads are started.

client = Featureflip::Client.for_testing(
"dark-mode" => true,
"pricing-tier" => "enterprise",
"rate-limit" => 500,
)
client.bool_variation("dark-mode", {}, false) # true
client.string_variation("pricing-tier", {}, "free") # "enterprise"
client.bool_variation("unknown", {}, false) # false (default)

Signature:

def self.for_testing(flags)
ParameterTypeDescription
flagsHashA hash mapping flag keys to their values
RSpec.describe MyService do
let(:client) { Featureflip::Client.for_testing("premium-feature" => true) }
it "enables premium feature" do
service = MyService.new(feature_client: client)
expect(service.premium_enabled?).to be true
end
end

All public types are available from the Featureflip namespace:

require "featureflip"
Featureflip::Client
Featureflip::Config
Featureflip::Models::EvaluationDetail
ExceptionDescription
Featureflip::ErrorBase exception for all SDK errors
Featureflip::InitializationErrorRaised when initialization fails or times out
Featureflip::ConfigurationErrorRaised for invalid configuration values