Ruby
The Shipeasy Ruby gem — fork-safe singleton, Rails railtie, local evaluation, configs, experiments, and metric tracking.
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 } }
endOutside 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
endA 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" }
endTrack 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_overridesErrors & 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)
endUse 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.