Shipeasy

Ruby

The Shipeasy Ruby gem — fork-safe singleton, Rails railtie, local evaluation, configs, experiments, and metric tracking.

Production readyOn this page · 4 min readUpdated · June 18, 2026Works with · Ruby 3.0+ · Rails · Sinatra · Hanami

Server-side gate evaluation, configs, experiments, and metric ingestion for Ruby. Evaluates locally against a background-polled blob — see the shared evaluation model.

Install

# Gemfile
gem "shipeasy"

Configure

Drop a config/initializers/shipeasy.rb with the server key, plus an optional attributes transform that maps your user object onto the Shipeasy attribute map. The engine is a fork-safe singleton — it spawns its own poll thread on first use, including post-fork Puma workers.

Shipeasy.configure do |c|
  c.api_key    = ENV.fetch("SHIPEASY_SERVER_KEY")
  c.public_key = ENV.fetch("SHIPEASY_CLIENT_KEY")  # for i18n view helpers
  c.attributes = ->(u) { { user_id: u.id, plan: u.plan } }
end

Outside Rails (Sinatra, Hanami, scripts) the same configure block works — the Rails view helpers are simply skipped.

Bind a client to the user

Construct a lightweight Shipeasy::Client from your user object — the attributes transform turns it into the evaluation context, so the getters take no user argument:

flags = Shipeasy::Client.new(current_user)

Evaluate a feature flag

if flags.get_flag("new_checkout")
  # ship it
end

A default: is returned only when the value can't be resolved (client not ready or gate absent) — a gate that evaluates false returns false:

flags.get_flag("new_checkout", default: true)

For the value plus the reason, use get_flag_detail — see Evaluation reasons:

detail = flags.get_flag_detail("new_checkout")
detail.value   # => true / false
detail.reason  # => "RULE_MATCH" / "DEFAULT" / ...

Read a dynamic config

color = flags.get_config("button_color", default: "blue")

Resolve an experiment

result = flags.get_experiment("checkout_cta", { label: "Buy now" })
if result.in_experiment
  result.group   # => "treatment"
  result.params  # => { label: "Buy now" }
end

Track an event

flags.track("checkout_completed", { revenue: 49.99 })

Serverless & anon visitors

For Lambda / Cloud Run, drive the engine directly and call init_once for one synchronous fetch (the per-user Shipeasy::Engine API also takes the user on every call for back-compat):

engine = Shipeasy::Engine.new(api_key: ENV.fetch("SHIPEASY_SERVER_KEY"))
engine.init_once
engine.get_flag("new_checkout", { user_id: "u_1" })

In Rails the railtie mounts Shipeasy::RackMiddleware, which mints the shared __se_anon_id cookie so logged-out traffic buckets like the browser. Bare Rack apps mount it with use Shipeasy::RackMiddleware.

Testing

Shipeasy::Engine.for_testing does zero network — no api*key, init/track are no-ops. Seed with override*\*and reset withclear_overrides. See Testing.

engine = Shipeasy::Engine.for_testing
engine.override_flag("new_checkout", true)
engine.get_flag("new_checkout", { user_id: "u_1" }) # => true
engine.clear_overrides

Errors & feedback

Report a handled exception with see() so it folds into the errors primitive with a one-sentence consequence — what feature broke and how it degraded. see() rides the same configure boot (server key); there is no separate error SDK or second key.

begin
  charge_card(order, prices)
rescue => e
  Shipeasy.see(e).causes_the("checkout").to("use cached prices").extras(order_id: order.id)
end

Use Shipeasy.see_violation("large query") for a non-exception problem, and Shipeasy.control_flow_exception(e).because("…") for expected control flow that should report nothing. Full grammar — consequence phrasing, control-flow exceptions, anti-patterns — is in Error reporting with see().

The in-app bug & feature report overlay is a standalone <script> tag you drop into your frontend — platform-agnostic, no server SDK required. See The devtools overlay.

Was this page helpful?
✎ Edit this page

On this page