Skip to content

setbit-io/setbit-ruby

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SetBit Ruby SDK

Official Ruby SDK for SetBit - Simple feature flags and A/B testing.

Features

  • βœ… 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

Installation

Add this line to your application's Gemfile:

gem 'setbit'

And then execute:

bundle install

Or install it yourself as:

gem install setbit

Quick Start

require '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 })

API Reference

Initialization

SetBit::Client.new(api_key:, tags: {}, base_url: "https://flags.setbit.io", logger: nil)

Parameters:

  • api_key (String, required): Your SetBit API key
  • tags (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 invalid
  • SetBit::APIError: If initial flag fetch fails

Example:

client = SetBit::Client.new(
  api_key: "pk_abc123",
  tags: { env: "production", app: "web", region: "us-east" }
)

enabled?(flag_name, user_id:, default: false)

Check if a flag is enabled. Returns true if the flag is globally enabled, false otherwise.

Parameters:

  • flag_name (String): Name of the flag
  • user_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
end

variant(flag_name, user_id:, default: "control")

Get the variant for an A/B test experiment or rollout flag.

Parameters:

  • flag_name (String): Name of the experiment or rollout flag
  • user_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
end

track(event_name, user_id:, flag_name: nil, variant: nil, metadata: {})

Track 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 conversion
  • variant (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")

refresh

Manually refresh flags from the API.

Returns: void

Raises:

  • SetBit::AuthError: If API key is invalid
  • SetBit::APIError: If API request fails

Example:

# Refresh flags if you know they've changed
client.refresh

Usage Examples

Boolean Flags

require '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)

Rollout Flags

# 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

A/B Testing

# 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)

Conversion Tracking

# 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"
  }
)

Error Handling

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

Rails Integration

# 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 %>

Sinatra Integration

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'
end

Background Jobs (Sidekiq)

class 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
end

Multi-Tenancy

class 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 feature

Development

Setup

git clone https://github.com/setbit/setbit-ruby.git
cd setbit-ruby
bundle install

Running Tests

# Run all tests
bundle exec rspec

# Run specific test file
bundle exec rspec spec/client_spec.rb

# Run with coverage
bundle exec rake coverage

Linting

# Run RuboCop
bundle exec rubocop

# Auto-fix issues
bundle exec rubocop -a

Generate Documentation

bundle exec yard doc

Error Handling

The SDK uses a fail-open philosophy - if something goes wrong, it returns safe default values rather than crashing your application.

Exception Types

  • SetBit::Error: Base exception for all SDK errors
  • SetBit::AuthError: Invalid API key (raised during initialization/refresh)
  • SetBit::APIError: API request failed (raised during initialization/refresh)

Behavior

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

API Endpoints

The SDK communicates with these SetBit API endpoints:

  • GET /api/sdk/flags - Fetch flags for given tags
  • POST /api/events - Send conversion events

Requirements

  • Ruby >= 2.6.0
  • No external runtime dependencies (uses standard library only!)

Support

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Roadmap

  • 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

About

Ruby SDK for SetBit feature flagging

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages