philiprehberger-feature_flag

Tests Gem Version Last updated

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:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT