Official Ruby SDK for SetBit - Simple feature flags and A/B testing.
- β Boolean Flags - Simple on/off feature toggles
- π§ͺ A/B Testing - Weighted variant distribution for experiments
- π Conversion Tracking - Track events and conversions
- π·οΈ Tag-Based Targeting - Target by environment, app, team, region, etc.
- π Fail-Open Design - Returns defaults if API is unreachable
- πͺΆ Zero Dependencies - Uses only Ruby standard library
- π Idiomatic Ruby - Follows Ruby conventions and best practices
Add this line to your application's Gemfile:
gem 'setbit'And then execute:
bundle installOr install it yourself as:
gem install setbitrequire 'setbit'
# Initialize the client
client = SetBit::Client.new(
api_key: "pk_your_api_key_here",
tags: { env: "production", app: "web" }
)
# Check a boolean flag (user_id required for analytics)
user_id = get_current_user_id
if client.enabled?("new-checkout", user_id: user_id)
show_new_checkout
else
show_old_checkout
end
# Rollout flag (gradual percentage-based rollout)
variant = client.variant("new-api", user_id: current_user.id)
if variant == "enabled"
use_new_api
else
use_old_api
end
# Get A/B test variant
variant = client.variant("pricing-experiment", user_id: current_user.id)
case variant
when "variant_a"
show_price_99
when "variant_b"
show_price_149
else # control
show_price_129
end
# Track conversions
client.track("purchase", user_id: current_user.id, metadata: { amount: 99.99 })SetBit::Client.new(api_key:, tags: {}, base_url: "https://flags.setbit.io", logger: nil)Parameters:
api_key(String, required): Your SetBit API keytags(Hash, optional): Tags for targeting flags (e.g.,{ env: "production", app: "web" })base_url(String, optional): API endpoint URL (useful for self-hosted instances)logger(Logger, optional): Custom logger instance
Raises:
SetBit::AuthError: If API key is invalidSetBit::APIError: If initial flag fetch fails
Example:
client = SetBit::Client.new(
api_key: "pk_abc123",
tags: { env: "production", app: "web", region: "us-east" }
)Check if a flag is enabled. Returns true if the flag is globally enabled, false otherwise.
Parameters:
flag_name(String): Name of the flaguser_id(String, required): User identifier (required for analytics and billing)default(Boolean): Value to return if flag not found (default:false)
Returns: Boolean - true if enabled, false otherwise
Note: This method returns whether the flag is globally enabled. For rollout flags, use variant() to check which rollout group the user is in.
Example:
# Check if flag is enabled (user_id required)
user_id = get_current_user_id
if client.enabled?("new-dashboard", user_id: user_id)
render_new_dashboard
else
render_old_dashboard
end
# With custom default
if client.enabled?("beta-feature", user_id: user_id, default: true)
show_beta_feature
endGet the variant for an A/B test experiment or rollout flag.
Parameters:
flag_name(String): Name of the experiment or rollout flaguser_id(String): User identifier (required)default(String): Variant to return if flag not found (default:"control")
Returns: String - Variant name
For experiments: "control", "variant_a", "variant_b", etc.
For rollout flags: "enabled" (user in rollout) or "disabled" (user not in rollout)
Example:
# A/B test experiment
variant = client.variant("button-color-test", user_id: current_user.id)
button_color = case variant
when "variant_a" then "blue"
when "variant_b" then "green"
else "red" # control
end
# Rollout flag (gradual percentage-based rollout)
variant = client.variant("new-api", user_id: current_user.id)
if variant == "enabled"
use_new_api # User is in the rollout group
else
use_old_api # User is not in the rollout group
endTrack a conversion event.
Parameters:
event_name(String): Name of the event (e.g.,"purchase","signup")user_id(String, required): User identifier (required for analytics and billing)flag_name(String, optional): Flag to associate with this conversionvariant(String, optional): Variant the user was assigned to (for A/B test attribution)metadata(Hash, optional): Additional event data
Returns: void
Note: This method fails silently - errors are logged but not raised.
Example:
user_id = current_user.id
# Track conversion with variant attribution (recommended for A/B tests)
variant = client.variant("pricing-test", user_id: user_id)
# ... later when user converts ...
client.track(
"purchase",
user_id: user_id,
flag_name: "pricing-test",
variant: variant,
metadata: { amount: 99.99, currency: "USD" }
)
# Track basic conversion
client.track("signup", user_id: user_id)
# Track with flag association only
client.track("purchase", user_id: user_id, flag_name: "checkout-experiment")Manually refresh flags from the API.
Returns: void
Raises:
SetBit::AuthError: If API key is invalidSetBit::APIError: If API request fails
Example:
# Refresh flags if you know they've changed
client.refreshrequire 'setbit'
client = SetBit::Client.new(
api_key: "pk_abc123",
tags: { env: "production", app: "web" }
)
# Simple feature toggle (boolean flag)
enable_dark_mode if client.enabled?("dark-mode")
# With default value
debug_mode = client.enabled?("debug-logging", default: false)# Gradual percentage-based rollout
# Use variant() to check which rollout group the user is in
variant = client.variant("new-api-v2", user_id: current_user.id)
if variant == "enabled"
use_api_v2 # User is in the rollout group
else
use_api_v1 # User is not in the rollout group
end
# Example: Rollout new checkout flow
checkout_variant = client.variant("new-checkout", user_id: current_user.id)
if checkout_variant == "enabled"
render_new_checkout
client.track("checkout_started", user_id: current_user.id, flag_name: "new-checkout")
else
render_old_checkout
end# Get variant for experiment
variant = client.variant("homepage-hero", user_id: current_user.id)
case variant
when "variant_a"
# Version A: Large hero image
render_hero(size: "large", style: "image")
when "variant_b"
# Version B: Video hero
render_hero(size: "large", style: "video")
when "variant_c"
# Version C: Minimal hero
render_hero(size: "small", style: "minimal")
else
# Control: Original hero
render_hero(size: "medium", style: "image")
end
# Track conversion for this experiment (pass variant for proper attribution)
client.track("signup", user_id: current_user.id, flag_name: "homepage-hero", variant: variant)# Track page view
client.track("page_view", metadata: { page: "/pricing" })
# Track user signup
client.track("signup", metadata: {
plan: "pro",
source: "landing_page"
})
# Track purchase with detailed metadata
client.track(
"purchase",
flag_name: "checkout-experiment",
metadata: {
amount: 149.99,
currency: "USD",
items: 3,
payment_method: "credit_card"
}
)require 'setbit'
begin
client = SetBit::Client.new(api_key: "invalid_key")
rescue SetBit::AuthError => e
puts "Invalid API key: #{e.message}"
# Fall back to default behavior
client = nil
end
# Client returns safe defaults if initialization failed
if client&.enabled?("new-feature")
show_new_feature
else
show_old_feature
end# config/initializers/setbit.rb
Rails.application.config.setbit = SetBit::Client.new(
api_key: ENV['SETBIT_API_KEY'],
tags: {
env: Rails.env,
app: "rails-app"
},
logger: Rails.logger
)
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def setbit
Rails.application.config.setbit
end
helper_method :setbit
end
# app/controllers/checkout_controller.rb
class CheckoutController < ApplicationController
def new
variant = setbit.variant("checkout-flow")
if variant == "one_page"
render :one_page_checkout
else
render :multi_step_checkout
end
end
def complete
setbit.track(
"purchase",
flag_name: "checkout-flow",
metadata: { amount: params[:amount] }
)
redirect_to thank_you_path
end
end
# app/views/layouts/application.html.erb
<% if setbit.enabled?("new-header") %>
<%= render "shared/new_header" %>
<% else %>
<%= render "shared/old_header" %>
<% end %>
require 'sinatra'
require 'setbit'
configure do
set :setbit, SetBit::Client.new(
api_key: ENV['SETBIT_API_KEY'],
tags: { env: ENV['RACK_ENV'], app: "sinatra-app" }
)
end
get '/' do
if settings.setbit.enabled?("new-homepage")
erb :homepage_v2
else
erb :homepage
end
end
post '/purchase' do
# Process purchase...
settings.setbit.track(
"purchase",
metadata: { amount: params[:amount] }
)
redirect '/thank-you'
endclass ProcessOrderJob
include Sidekiq::Worker
def perform(order_id)
order = Order.find(order_id)
# Use SetBit in background job
client = SetBit::Client.new(
api_key: ENV['SETBIT_API_KEY'],
tags: { env: Rails.env, app: "worker" }
)
if client.enabled?("advanced-fraud-detection")
run_advanced_fraud_check(order)
else
run_basic_fraud_check(order)
end
client.track("order_processed", metadata: { order_id: order_id })
end
endclass ApplicationController < ActionController::Base
def setbit
@setbit ||= SetBit::Client.new(
api_key: ENV['SETBIT_API_KEY'],
tags: {
env: Rails.env,
app: "web",
tenant: current_tenant.slug
}
)
end
helper_method :setbit
end
# Now flags can be targeted per tenant
# tags: { env: "production", tenant: "acme-corp" } β Show feature
# tags: { env: "production", tenant: "other-corp" } β Hide featuregit clone https://github.com/setbit/setbit-ruby.git
cd setbit-ruby
bundle install# Run all tests
bundle exec rspec
# Run specific test file
bundle exec rspec spec/client_spec.rb
# Run with coverage
bundle exec rake coverage# Run RuboCop
bundle exec rubocop
# Auto-fix issues
bundle exec rubocop -abundle exec yard docThe SDK uses a fail-open philosophy - if something goes wrong, it returns safe default values rather than crashing your application.
SetBit::Error: Base exception for all SDK errorsSetBit::AuthError: Invalid API key (raised during initialization/refresh)SetBit::APIError: API request failed (raised during initialization/refresh)
| Method | Error Behavior |
|---|---|
new |
Raises exception if API key invalid or flags can't be fetched |
enabled? |
Returns default value if flag not found |
variant |
Returns default variant if flag not found |
track |
Logs error but does not raise exception |
refresh |
Raises exception if refresh fails |
The SDK communicates with these SetBit API endpoints:
- GET
/api/sdk/flags- Fetch flags for given tags - POST
/api/events- Send conversion events
- Ruby >= 2.6.0
- No external runtime dependencies (uses standard library only!)
- π Documentation
- π¬ Discord Community
- π§ Email: support@setbit.io
- π Report Issues
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Add memoization options for variant selection
- Support for local flag evaluation
- Streaming flag updates via WebSocket
- Performance metrics and benchmarks
Made with β€οΈ by the SetBit team