Flagstack Ruby Client
Ruby client for Flagstack feature flag management. Drop-in replacement for Flipper Cloud.
Installation
Add to your Gemfile:
gem "flagstack"
gem "flipper"
gem "flipper-active_record" # Recommended: for persistent local storage
Then run the generator:
rails generate flagstack:install
Quick Start
Set your API token:
export FLAGSTACK_TOKEN=fs_live_your_token_here
That's it! Flagstack auto-configures when FLAGSTACK_TOKEN is present. Your existing Flipper code works unchanged:
Flipper.enabled?(:new_checkout)
Flipper.enabled?(:beta_feature, current_user)
How It Works
Flagstack mirrors Flipper Cloud's architecture:
+-----------------+
| Flagstack |
| (cloud server) |
+--------+--------+
|
| sync (every 10-30s)
v
+--------+--------+
| Local Adapter |
| (ActiveRecord |
| or Memory) |
+--------+--------+
|
| all reads
v
+--------+--------+
| Your App |
| Flipper.enabled?|
+-----------------+
- Flagstack is the source of truth - Manage flags in the Flagstack UI
- Data syncs to your local adapter - Background poller keeps local data fresh
- All reads are local - Zero network latency for
enabled?checks - Works offline - If Flagstack is down, reads continue from local adapter
Configuration
Basic Configuration
# config/initializers/flagstack.rb
Flagstack.configure do |config|
config.token = ENV["FLAGSTACK_TOKEN"]
config.sync_interval = 10 # seconds (minimum 10)
end
Full Options
Flagstack.configure do |config|
# Required
config.token = ENV["FLAGSTACK_TOKEN"]
# Server (default: https://flagstack.io)
config.url = ENV["FLAGSTACK_URL"]
# Sync interval in seconds (default: 10, minimum: 10)
config.sync_interval = 30
# Sync method: :poll (background thread) or :manual
config.sync_method = :poll
# Telemetry (usage metrics)
config.telemetry_enabled = true # default: true
config.telemetry_interval = 60 # seconds between submissions
# HTTP timeouts in seconds
config.read_timeout = 5
config.open_timeout = 2
config.write_timeout = 5
# Local adapter (auto-detected if flipper-active_record is present)
# Falls back to Memory adapter if not specified
config.local_adapter = Flipper::Adapters::ActiveRecord.new
# Logging
config.logger = Rails.logger
# Debug HTTP requests
config.debug_output = $stderr
# Instrumentation (for monitoring)
config.instrumenter = ActiveSupport::Notifications
end
Telemetry
Flagstack collects anonymous usage metrics to help you understand feature flag usage patterns. This data powers the Analytics dashboard in Flagstack.
What's Collected
- Feature key (which flag was checked)
- Result (enabled/disabled)
- Timestamp (rounded to the minute)
- Count (aggregated locally before submission)
Disabling Telemetry
Flagstack.configure do |config|
config.token = ENV["FLAGSTACK_TOKEN"]
config.telemetry_enabled = false
end
Usage
With Rails (Recommended)
When FLAGSTACK_TOKEN is set, Flagstack automatically configures itself. You can use either the Flagstack API or Flipper directly:
# Native Flagstack API (recommended for new projects)
Flagstack.enabled?(:new_checkout)
Flagstack.enabled?(:beta_feature, current_user)
# Flipper-compatible - existing code works unchanged
Flipper.enabled?(:new_checkout)
Flipper.enabled?(:beta_feature, current_user)
# Feature objects work too
Flagstack[:new_checkout].enabled?
Flipper[:new_checkout].enabled?
Manual Configuration
# config/initializers/flagstack.rb
flipper = Flagstack.configure do |config|
config.token = ENV["FLAGSTACK_TOKEN"]
end
# Use the returned Flipper instance directly
flipper.enabled?(:new_checkout)
# Or access it later
Flagstack.flipper.enabled?(:new_checkout)
Without Global State
For multi-tenant apps or testing:
# Create separate instances (doesn't affect Flagstack.configuration)
flipper = Flagstack.new(token: "fs_live_xxx")
flipper.enabled?(:feature)
flipper.enabled?(:feature, current_user)
Actor Setup
For percentage rollouts and actor targeting, your user model needs a flipper_id:
class User < ApplicationRecord
# Flipper expects this method for actor-based features
def flipper_id
"User_#{id}"
end
end
# Then use it
Flipper.enabled?(:beta_feature, current_user)
Local Features
You can still create local-only features that aren't synced from Flagstack:
# Enable a local feature
Flipper.enable(:local_only_feature)
# It works alongside Flagstack features
Flipper.enabled?(:local_only_feature) # true
Flipper.enabled?(:flagstack_feature) # from Flagstack
Note: Flagstack sync only affects features that exist in Flagstack. Your local features are preserved.
Token Types
| Prefix | Environment | Use |
|---|---|---|
fs_live_ |
Production | Live traffic |
fs_test_ |
Staging | Pre-production testing |
fs_dev_ |
Development | Shared development |
fs_personal_ |
Personal | Your local machine |
API Reference
Configuration
Flagstack.configure { |config| }
Configure and return a Flipper instance. Sets Flagstack.configuration and Flagstack.flipper.
Flagstack.new(options)
Create a standalone Flipper instance without affecting global state.
Flagstack.flipper
Returns the configured Flipper instance (after configure).
Feature Flag Checks
Flagstack.enabled?(feature, actor = nil)
Check if a feature is enabled, optionally for a specific actor.
Flagstack.enabled?(:new_checkout)
Flagstack.enabled?(:beta_feature, current_user)
Flagstack.disabled?(feature, actor = nil)
Check if a feature is disabled.
Flagstack.disabled?(:maintenance_mode)
Flagstack[feature]
Access a feature object for method chaining.
Flagstack[:new_checkout].enabled?
Flagstack[:new_checkout].enabled?(current_user)
Feature Flag Management
Flagstack.enable(feature) / Flagstack.disable(feature)
Enable or disable a feature globally.
Flagstack.enable(:new_feature)
Flagstack.disable(:old_feature)
Flagstack.enable_actor(feature, actor) / Flagstack.disable_actor(feature, actor)
Enable or disable a feature for a specific actor.
Flagstack.enable_actor(:beta_feature, current_user)
Flagstack.enable_group(feature, group) / Flagstack.disable_group(feature, group)
Enable or disable a feature for a registered group.
Flagstack.register(:admins) { |actor| actor.admin? }
Flagstack.enable_group(:admin_tools, :admins)
Flagstack.enable_percentage_of_actors(feature, percentage)
Enable a feature for a percentage of actors (deterministic based on actor ID).
Flagstack.enable_percentage_of_actors(:new_feature, 25) # 25% of users
Flagstack.features
List all features.
Flagstack.features.each { |f| puts f.key }
Utilities
Flagstack.sync
Force a sync from Flagstack to the local adapter.
Flagstack.health_check
Check connectivity to Flagstack server. Returns { ok: true/false, message: "..." }.
result = Flagstack.health_check
if result[:ok]
puts "Connected: #{result[:message]}"
else
puts "Error: #{result[:message]}"
end
Flagstack.shutdown
Gracefully stop the poller and flush any pending telemetry. Called automatically on Rails shutdown.
Flagstack.reset!
Reset everything (clears configuration, stops poller). Useful for testing.
Testing
In your test setup:
RSpec.configure do |config|
config.before(:each) do
Flagstack.reset!
end
end
For isolated tests, use Flagstack.new with a test token or stub the HTTP calls:
# With WebMock
stub_request(:get, "https://flagstack.io/api/v1/sync")
.to_return(
status: 200,
body: { features: [{ key: "test_feature", enabled: true, gates: {} }] }.to_json
)
stub_request(:post, "https://flagstack.io/api/v1/telemetry")
.to_return(status: 202)
flipper = Flagstack.new(token: "fs_test_xxx")
expect(flipper.enabled?(:test_feature)).to be true
Migrating from Flipper Cloud
Flagstack is designed as a drop-in replacement:
- Replace
FLIPPER_CLOUD_TOKENwithFLAGSTACK_TOKEN - Replace
gem "flipper-cloud"withgem "flagstack" - Your
Flipper.enabled?calls work unchanged
License
MIT