philiprehberger-feature_flag
Minimal feature flag system with YAML, ENV, and in-memory backends
Requirements
- Ruby >= 3.1
Installation
Add to your Gemfile:
gem "philiprehberger-feature_flag"
Or install directly:
gem install philiprehberger-feature_flag
Usage
Configuration
require "philiprehberger/feature_flag"
# In-memory backend (default)
Philiprehberger::FeatureFlag.configure do |c|
c.use(:memory)
end
# ENV backend (reads FEATURE_* environment variables)
Philiprehberger::FeatureFlag.configure do |c|
c.use(:env)
end
# YAML backend
Philiprehberger::FeatureFlag.configure do |c|
c.use(:yaml, path: 'config/features.yml')
end
Checking flags
# Simple boolean check
if Philiprehberger::FeatureFlag.enabled?(:dark_mode)
render_dark_theme
end
# Percentage rollout (requires user_id)
if Philiprehberger::FeatureFlag.enabled?(:new_checkout, user_id: current_user.id)
render_new_checkout
end
A/B variants
variant = Philiprehberger::FeatureFlag.variant(:button_color, user_id: current_user.id)
# => 'red', 'blue', or 'green' (consistent per user)
Dependencies
Gate a flag behind another flag. The dependent flag is only enabled when its required flag is also enabled.
flags = Philiprehberger::FeatureFlag
flags.depends_on(:new_ui, requires: :beta_users)
# :new_ui will only be enabled if :beta_users is also enabled
flags.enabled?(:new_ui) # => false (unless :beta_users is enabled)
Dependencies can be chained:
flags.depends_on(:beta, requires: :alpha)
flags.depends_on(:gamma, requires: :beta)
# :gamma requires :beta, which requires :alpha
Scheduling
Enable or disable flags at specific times. Flags are only active within the scheduled window.
flags = Philiprehberger::FeatureFlag
flags.schedule(:holiday_banner,
enable_at: Time.new(2026, 12, 24),
disable_at: Time.new(2026, 12, 26))
# Only enabled between Dec 24 and Dec 26
flags.enabled?(:holiday_banner)
You can also use enable_at or disable_at individually:
# Enable after a certain time (no end)
flags.schedule(:new_feature, enable_at: Time.new(2026, 4, 1))
# Disable after a certain time (active until then)
flags.schedule(:old_feature, disable_at: Time.new(2026, 6, 1))
Metrics
Track how often each flag is evaluated and whether it was enabled or disabled.
flags = Philiprehberger::FeatureFlag
flags.enabled?(:feature_x)
flags.enabled?(:feature_x)
flags.metrics(:feature_x)
# => { checks: 2, enabled: 1, disabled: 1 }
Metrics are reset when reset! is called.
User targeting
Whitelist specific users for a flag, independent of the backend value.
flags = Philiprehberger::FeatureFlag
flags.enable_for(:feature, users: ["user_1", "user_2"])
flags.enabled?(:feature, user: "user_1") # => true
flags.enabled?(:feature, user: "user_3") # => false (falls back to backend)
Remove users from the whitelist:
flags.disable_for(:feature, users: ["user_1"])
Groups
Group flags together for bulk enable/disable operations.
flags = Philiprehberger::FeatureFlag
flags.group(:beta, [:feature_a, :feature_b, :feature_c])
flags.enable_group(:beta) # enables all three flags
flags.disable_group(:beta) # disables all three flags
flags.group_flags(:beta) # => [:feature_a, :feature_b, :feature_c]
YAML file format
dark_mode: true
beta_search: false
new_checkout:
percentage: 25
button_color:
variants:
- red
- blue
- green
ENV backend
Set environment variables prefixed with FEATURE_:
export FEATURE_DARK_MODE=true
export FEATURE_BETA_SEARCH=false
Test helper
Philiprehberger::FeatureFlag.with(:dark_mode, true) do
# flag is forced to true within this block
assert Philiprehberger::FeatureFlag.enabled?(:dark_mode)
end
# original value restored after block
Reloading
Philiprehberger::FeatureFlag.reload!
Introspecting known flags
List every flag name the configuration knows about — pulling from the backend, registered dependencies, schedules, targeted users, and groups. The result is deduplicated, sorted ascending, and returned as an array of symbols.
flags = Philiprehberger::FeatureFlag
flags.configuration.backend.set(:dark_mode, true)
flags.schedule(:holiday_banner, enable_at: Time.new(2026, 12, 24))
flags.depends_on(:new_ui, requires: :beta_users)
flags.enable_for(:vip_only, users: %w[user_1])
flags.flag_names
# => [:beta_users, :dark_mode, :holiday_banner, :new_ui, :vip_only]
API
| Method | Description |
|---|---|
| `.configure { \ | c\ |
.enabled?(flag, user_id: nil, user: nil) |
Check if a flag is enabled |
.variant(flag, user_id:) |
Get A/B variant for a user |
.with(flag, value) { } |
Override a flag in a block |
.reload! |
Reload flags from the backend |
.reset! |
Reset configuration and overrides |
.flag_names |
Sorted, deduplicated list of all known flag names |
.depends_on(flag, requires:) |
Declare a flag dependency |
.schedule(flag, enable_at:, disable_at:) |
Schedule flag activation window |
.metrics(flag) |
Get check/enabled/disabled counts |
.enable_for(flag, users:) |
Whitelist users for a flag |
.disable_for(flag, users:) |
Remove users from whitelist |
.group(name, flags) |
Define a flag group |
.enable_group(name) |
Enable all flags in a group |
.disable_group(name) |
Disable all flags in a group |
.group_flags(name) |
List flags in a group |
Development
bundle install
bundle exec rspec
bundle exec rubocop
Support
If you find this project useful: