The official Convert Experiences FullStack Ruby SDK — server-side A/B testing, feature flags, and personalizations for Ruby applications. Bucketing-compatible with the Convert JavaScript SDK. Zero runtime dependencies (stdlib only).
ConvertSdk.create
raises (an ArgumentError).# Gemfile
gem "convert_sdk"
gem install convert_sdk
The complete flow — build a client, create a per-visitor context, decide an experience, act on the result, track a conversion, and flush — in one copy-pasteable block:
require "convert_sdk"
# 1. Build ONE client at boot and reuse it for the life of the process.
# (Fetch mode: pass an sdk_key. Direct-data mode: pass a pre-fetched `data:`.)
CONVERT_SDK = ConvertSdk.create(sdk_key: ENV.fetch("CONVERT_SDK_KEY"))
# 2. One context per visitor (per web request / per job). Cheap — no network,
# no thread.
context = CONVERT_SDK.create_context("visitor-123", { "country" => "US" })
# 3. Decide an experience. Returns a BucketedVariation on a hit, or a Sentinel
# on a miss — NEVER raises, NEVER a bare nil.
variation = context.run_experience("homepage-test")
# 4. Act on the result. `variation&.key` is the variation key on a hit and nil
# on a miss (a Sentinel's #key is always nil), so a single `case` covers both.
case variation&.key
when nil then render_default # business miss — show the control
when "treatment" then render_treatment
else render_variation(variation.key)
end
# 5. Track a conversion with revenue. Deduplicated per visitor per goal.
context.track_conversion("purchase", goal_data: { amount: 49.99, transaction_id: "tx-1" })
# 6. Flush queued events synchronously. In long-running servers the background
# timer also drains; call flush explicitly before a process exits (Lambda/CLI).
CONVERT_SDK.flush
Production wiring per runtime — Rails, Sidekiq, AWS Lambda, and CLI recipes live in the wiki: Fork Safety & Runtime Recipes and Quickstart.
Fork safety is the SDK’s flagship guarantee, so it leads the docs.
The claim: build the client once, let your server fork workers, and events
are delivered from every forked worker — with zero fork-handling code in your
app. No postfork, no on_worker_boot hook required.
How it works:
require "convert_sdk" the SDK installs a single Process._fork hook
(its only global mutation). The hook is cheap and starts no threads.preload_app!) carries no thread state across the
fork._fork detection plus
PID-guarded flush boundaries automatically re-arm the client (timers
re-start lazily, the queue’s process ownership resets) — so the worker decides
and delivers on its own.When you need postfork: only for setups that bypass Process._fork
entirely (or daemonize via Process.daemon), or if you prefer an explicit
re-arm (LaunchDarkly-style). The
fork/daemon matrix in the troubleshooting guide
spells out exactly which runtimes are automatic and which need an explicit
CONVERT_SDK.postfork call. The four quickstarts ship the right wiring for each.
The full public API is documented in the wiki and the YARD API reference. The
entry point is ConvertSdk.create (factory); it returns a Client with
#create_context, #flush, #postfork, and #on. Each Context exposes
#run_experience, #run_feature, #track_conversion, and related methods.
See Code Examples and
the API reference (YARD) for full
signatures.
Decisioning methods never raise and never return a bare nil for a
business miss. They return a value object:
BucketedVariation (or BucketedFeature): #key
is the real key, #error? is false.Sentinel: #key is always nil, #error?
is always true, and #to_s is the wire string.This is why the documented branch pattern works for both cases at once:
case (variation = context.run_experience("homepage-test")).key
when nil then render_default # Sentinel — a business miss
else render_variation(variation.key)
end
For features, branch on #status instead (a feature miss is a DISABLED
BucketedFeature, never a sentinel):
feature = context.run_feature("new-checkout")
if feature.status == ConvertSdk::FeatureStatus::ENABLED
render_new_checkout(feature.variables["headline"])
else
render_legacy_checkout
end
All configuration options are passed as keyword arguments to ConvertSdk.create.
See the Configuration wiki page
for the full option table with defaults. Pass data_refresh_interval: nil and
flush_interval: nil for Lambda/CLI (timer-off mode).
Sticky bucketing and goal deduplication persist through a store port (default:
in-process MemoryStore). See
Configuration for
the RedisStore recipe and custom store duck-typing contract.
Full developer documentation lives in the Convert Ruby SDK wiki:
bundle install # install dev/test dependencies
bundle exec rake # the default task: RSpec + RuboCop
bundle exec rbs -r net-http -r uri -r json -I sig validate # validate RBS signatures
bundle exec steep check # static type check
Publishing is handled exclusively by the OIDC release workflow — there is no
rake release task. See CONTRIBUTING.md for the release
process.
Apache-2.0. See LICENSE.