A lightweight, real-time feature flag SDK for Ruby applications. Thread-safe, Rails-friendly, and built for production.
- 🚀 Real-time Updates - Instant flag changes via Server-Sent Events
- 🧵 Thread-Safe - Built for Puma, Sidekiq, and multi-threaded environments
- 💾 Smart Caching - Works offline with memory or Redis support
- 🔒 Production Ready - 214 passing tests with comprehensive coverage
- 🌐 Universal - Works with Rails, Sinatra, Hanami, and standalone Ruby
- ♻️ Auto-reconnection - Resilient connection handling
Add to your Gemfile:
gem 'togglecraft'Or install directly:
gem install togglecraftrequire 'togglecraft'
# Initialize client
client = ToggleCraft::Client.new(sdk_key: 'your-sdk-key')
# Connect and wait for flags
client.connect
client.wait_for_ready
# Use your flags
if client.enabled?('new-feature', user: { id: current_user.id })
  # Feature is enabled
end# config/initializers/togglecraft.rb
Rails.application.config.togglecraft = ToggleCraft::Client.new(
  sdk_key: ENV['TOGGLECRAFT_SDK_KEY'],
  cache_adapter: :memory,
  logger: Rails.logger,
  debug: Rails.env.development?
)
Rails.application.config.togglecraft.connect
at_exit do
  Rails.application.config.togglecraft.destroy
endThen use in your controllers:
class DashboardController < ApplicationController
  def index
    if togglecraft.enabled?('premium-dashboard', user: { id: current_user.id })
      render :premium
    else
      render :standard
    end
  end
  private
  def togglecraft
    Rails.application.config.togglecraft
  end
endSimple on/off feature toggles:
if client.enabled?('dark-mode', user: { id: '123' })
  enable_dark_mode
endA/B/n testing with multiple variants:
variant = client.variant('checkout-flow', user: { id: '123' })
case variant
when 'one-page'
  render_one_page_checkout
when 'multi-step'
  render_multi_step_checkout
else
  render_default_checkout
endGradual feature rollouts:
if client.in_percentage?('new-algorithm', user: { id: '123' })
  use_new_algorithm
else
  use_legacy_algorithm
endclient = ToggleCraft::Client.new(
  # Required
  sdk_key: 'your-sdk-key',              # Get from ToggleCraft dashboard
  # Optional - Common settings
  enable_cache: true,                   # Enable caching (default: true)
  cache_adapter: :memory,               # :memory or :redis (default: :memory)
  cache_ttl: 300,                       # Cache TTL in seconds (default: 5 minutes)
  debug: false                          # Enable debug logging (default: false)
)Need more control? See Advanced Configuration →
The context object provides data for targeting rules:
context = {
  user: {
    id: 'user-123',                     # Required for consistent evaluation
    email: 'user@example.com',
    plan: 'premium',
    # Add any custom attributes you need
    role: 'admin',
    company_id: 'acme-corp'
  },
  request: {
    ip: '192.168.1.1',
    country: 'US'
  },
  device: {
    type: 'mobile',
    os: 'iOS'
  }
}
client.enabled?('premium-feature', context)You can add any custom properties - the SDK evaluates all attributes using dot notation (e.g., user.role, request.country).
# config/initializers/togglecraft.rb
Rails.application.config.togglecraft = ToggleCraft::Client.new(
  sdk_key: ENV['TOGGLECRAFT_SDK_KEY'],
  cache_adapter: :memory,
  logger: Rails.logger
)Full Rails Integration Guide →
class FeatureWorker
  include Sidekiq::Worker
  def togglecraft
    @togglecraft ||= ToggleCraft::Client.new(
      sdk_key: ENV['TOGGLECRAFT_SDK_KEY'],
      share_connection: true  # Share connection with other workers
    )
  end
  def perform(user_id)
    togglecraft.connect unless togglecraft.connected?
    if togglecraft.enabled?('batch-processing', user: { id: user_id })
      # Use new batch processing
    end
  end
endFull Sidekiq Integration Guide →
Listen for real-time updates:
# Flags are ready
client.on(:ready) do
  puts 'Client ready!'
end
# Flags updated in real-time
client.on(:flags_updated) do |flags|
  puts "Flags updated: #{flags.keys.join(', ')}"
end
# Connection lost
client.on(:disconnected) do
  puts 'Disconnected - using cached flags'
end
# Error occurred
client.on(:error) do |error|
  logger.error "ToggleCraft error: #{error}"
endAlways provide default values and handle errors gracefully:
# Safe evaluation with defaults
is_enabled = client.enabled?('feature', context, default: false)
# Returns false if flag doesn't exist or on error
# Handle connection errors
begin
  client.connect
rescue StandardError => e
  logger.error "Failed to connect: #{e}"
  # App still works with cached values or defaults
end- Initialize Once - Create a single client instance and reuse it
- Always Provide Context - Include at least user.idfor consistent evaluation
- Use Default Values - Handle missing flags gracefully
- Clean Up on Shutdown - Call client.destroywhen closing
# On application shutdown
at_exit do
  client.destroy
endCore Methods:
- enabled?(flag_key, context = {}, default: false)- Check boolean flags
- variant(flag_key, context = {}, default: nil)- Get multivariate variant
- in_percentage?(flag_key, context = {}, default: false)- Check percentage rollout
Connection:
- connect- Connect to SSE server
- disconnect- Disconnect from server
- ready?- Check if client has flags loaded
- wait_for_ready(timeout: 5)- Wait for client to be ready
Power users can customize:
- Connection pooling and reconnection strategies
- Scheduled rollout stages with automatic transitions
- Redis cache adapter
- Hybrid reconnection with exponential backoff
Client not connecting?
- Verify your SDK key is correct
- Check that you called connectbefore using the client
- Ensure your firewall allows connections to sse.togglecraft.io
Flags not updating?
- Verify the SSE connection is established (client.connected?)
- Check the logs for error messages
- Enable debug mode: debug: true
- Ruby 3.0+
- Dependencies:
- concurrent-ruby(~> 1.2) - Thread-safe data structures
- http(~> 5.0) - HTTP client for API requests
- semantic(~> 1.6) - Semantic version comparison
 
This SDK is fully thread-safe and production-ready for:
- Puma - Multi-threaded Rails server
- Sidekiq - Background job processing
- Any multi-threaded Ruby environment
All critical sections use Concurrent::Map, Mutex, and Concurrent::AtomicBoolean for thread safety.
- API Reference - Complete API documentation
- Advanced Features - Connection pooling, rollout stages, custom configuration
- Framework Integration - Rails, Sidekiq, and other framework guides
- Troubleshooting - Common issues and solutions
- Security - Security best practices and policies
MIT
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
- Documentation: GitHub Repository
- Issues: GitHub Issues
- Security: See SECURITY.md for reporting vulnerabilities