FlagDash Ruby SDK
Feature flags, remote config, AI configs, translations and experiments for Ruby 3.1+.
Standard library only — no runtime dependencies.
Installation
gem "flagdash"
Or straight from GitHub, pinned to an immutable tag:
gem "flagdash", github: "flagdash/flagdash-ruby", tag: "v0.1.0"
Quick start
require "flagdash"
client = FlagDash::Client.new(sdk_key: ENV.fetch("FLAGDASH_SDK_KEY"))
if client.flag("checkout-v2", default: false, context: {user_id: "alice"})
# new checkout
end
client.close
API key tiers
The key decides which project and environment you read, and what you may
reach. There is no environment argument anywhere in this SDK — the key
carries it.
| Key | Prefix | Reaches |
|---|---|---|
| Client | pk_ |
Flag values and configs. Safe in a browser or mobile app. |
| Server | sk_ |
The above, plus targeting rules, translations and experiments. Keep it server-side. |
Configuration
client = FlagDash::Client.new(
sdk_key: ENV.fetch("FLAGDASH_SDK_KEY"),
base_url: "https://flagdash.io", # self-hosted? point it here
timeout: 5, # seconds per request
cache_ttl: 60, # seconds; 0 disables caching
region: "eu-west-1", # omit to auto-detect
transport: nil # inject for tests
)
region is detected from FLY_REGION, AWS_REGION and friends when omitted,
so region-scoped targeting works with no wiring.
Feature flags
# One flag, with the fallback used whenever FlagDash cannot be reached.
client.flag("checkout-v2", default: false, context: {user_id: "alice"})
# Every flag for this context in one request.
client.all_flags(context: {user_id: "alice", country: "GB"})
# Why did it resolve that way?
detail = client.flag_detail("checkout-v2", context: {user_id: "alice"})
detail.value # => true
detail.reason # => "rule_match"
detail.variation # => "treatment"
# Flag metadata without evaluating anything.
client.list_flags
Context
Both shapes work. A nested user: hash is flattened — id becomes user_id
and every other key becomes user_<key>:
client.flag("beta", context: {user_id: "alice", country: "GB"})
client.flag("beta", context: {user: {id: "alice", plan: "premium"}})
Send an identifier (user_id, unit_id, or user: {id:}) whenever you
want a stable answer. Percentage rollouts and A/B variations hash it, so an
anonymous context re-rolls on every call by design.
Note that flag without a context serves from the cached all_flags payload;
with a context it asks for a fresh evaluation.
Remote config
client.config("rate_limit", default: 100)
client.get_config("rate_limit") # full record, not just the value
client.list_configs
AI configs
Prompts, agents, skills and rules, versioned per environment and editable without a deploy.
client.ai_config("support-agent.md")
client.list_ai_configs(file_type: "agent")
client.list_ai_configs(folder: "support") # :any for every folder
Translations
client.translation("checkout.greeting", locale: "fr", default: "Hello",
variables: {name: "Alice"})
The key is namespace.message. {placeholders} come from variables, and the
default is returned whenever the catalogue, namespace or message is missing — a
lookup never raises.
Experiments
assignment = client.experiment("checkout-redesign", context: {user_id: "alice"})
if assignment && assignment["variant"] == "treatment"
# ...
end
client.track_experiment_metric(
experiment_key: "checkout-redesign",
event_name: "purchase",
user_id: "alice",
value: 42.50,
properties: {currency: "GBP"}
)
experiment returns nil for a context with no identifier — an assignment
that cannot be stable is worse than none.
Metrics are buffered in memory and only touch the network on flush or
close:
client.flush # send now
client.close # flush and release the connection
In a long-running process call flush periodically; in a request/response
cycle, close at the end is enough.
Caching
Reads are cached in memory for cache_ttl seconds (60 by default), so a burst
of flag calls costs one request.
client.clear_cache
Failure behaviour
This SDK deliberately splits the two cases:
- Evaluation reads return your default.
flag,configandtranslationdegrade to the fallback rather than raising, so an outage cannot take a request path down with it. - Metadata lists raise.
list_flags,list_configsandlist_ai_configsare operational calls, and swallowing their failure would hide a bad key or a wrong base URL behind an empty array.
Security
Keep the server key out of anything you ship to a browser or a phone. Grant a key only the read scopes it needs; a client key never receives targeting rules, so the client cannot see who else you are targeting.
License
MIT