Skip to content

A Ruby gem which adds simple feature flag functionality.

License

Notifications You must be signed in to change notification settings

espago/simple_feature_flags

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simple Feature Flags

Fast, simple and reliable feature flags using Redis or local memory in your Rails app.

Table of Contents

[[TOC]]

Installation

Gem installation

Add this line to your application's Gemfile:

gem 'simple_feature_flags'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install simple_feature_flags

Generate config files

This gem uses it's custom generator to make configuration easier for you.

Rails apps

  1. Navigate to the root directory of your Rails project

  2. Generate config files

     $ simple_feature_flags -g
     # or
     $ simple_feature_flags --generate

This should generate an initializer config/initializers/simple_feature_flags.rb

# config/initializers/simple_feature_flags.rb

# frozen_string_literal: true
# Redis has 16 DBs (0 to 15)

FEATURE_FLAGS = if ::Rails.env.test?
                  # Use TestRamStorage in tests to make them faster
                  ::SimpleFeatureFlags::TestRamStorage.new("#{::Rails.root.to_s}/config/simple_feature_flags.yml")
                else
                  redis = ::Redis.new(host: '127.0.0.1', port: 6379, db: 0)
                  # We recommend using the `redis-namespace` gem to avoid key conflicts with Sidekiq or Resque
                  # redis = ::Redis::Namespace.new(:simple_feature_flags, redis: redis)

                  ::SimpleFeatureFlags::RedisStorage.new(redis, "#{::Rails.root.to_s}/config/simple_feature_flags.yml")
                end

This initializer in turn makes use of the generated config file config/simple_feature_flags.yml

---
# Feature Flags that will be created if they don't exist already
:mandatory:
# example flag - it will be created with these properties if there is no such flag in Redis/RAM
# - name: example
#   active: 'globally' # %w[globally partially false] 'false' is the default value
#   description: example

- name: example_flag
  description: This is an example flag which will be automatically added when you start your app (it will be disabled)

- name: example_active_flag
  active: 'globally'
  description: This is an example flag which will be automatically added when you start your app (it will be enabled)

# nothing will happen if flag that is to be removed does not exist in Redis/RAM
# An array of Feature Flag names that will be removed on app startup
:remove:
- flag_to_be_removed

Non-Rails apps

  1. Navigate to the root directory of your project

  2. Generate config files

     $ simple_feature_flags -g --no-rails
     # or
     $ simple_feature_flags --generate --no-rails

Usage

This gem provides an easy way of dealing with feature flags. At this point in time it only supports global feature flags stored either in RAM or Redis.

Storage types/Storage adapters

All storage adapters have the same API for dealing with feature flags. The only difference is in how they store data.

SimpleFeatureFlags::RedisStorage

This class makes use of Redis to store feature flag data. You can make use of it like so:

require 'redis'

redis = ::Redis.new
config_file = "#{::Rails.root.to_s}/config/simple_feature_flags.yml"

FEATURE_FLAGS = ::SimpleFeatureFlags::RedisStorage.new(redis, config_file)

SimpleFeatureFlags::RamStorage

This class stores all feature flag data in a simple Ruby ::Hash. You can make use of it like so:

config_file = "#{::Rails.root.to_s}/config/simple_feature_flags.yml"

FEATURE_FLAGS = ::SimpleFeatureFlags::RamStorage.new(config_file)

Functionality

Activate a feature

Activates a feature in the global scope

FEATURE_FLAGS.active?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
FEATURE_FLAGS.active_partially?(:feature_name) #=> false

FEATURE_FLAGS.inactive?(:feature_name) #=> true
FEATURE_FLAGS.inactive_globally?(:feature_name) #=> true
FEATURE_FLAGS.inactive_partially?(:feature_name) #=> true

FEATURE_FLAGS.activate(:feature_name) # or FEATURE_FLAGS.activate_globally(:feature_name)

FEATURE_FLAGS.active?(:feature_name) #=> true
FEATURE_FLAGS.active_globally?(:feature_name) #=> true
FEATURE_FLAGS.active_partially?(:feature_name) #=> false

FEATURE_FLAGS.inactive?(:feature_name) #=> false
FEATURE_FLAGS.inactive_globally?(:feature_name) #=> false
FEATURE_FLAGS.inactive_partially?(:feature_name) #=> true

Deactivate a feature

Deactivates a feature in the global scope

FEATURE_FLAGS.active?(:feature_name) #=> true
FEATURE_FLAGS.inactive?(:feature_name) #=> false

FEATURE_FLAGS.deactivate(:feature_name)

FEATURE_FLAGS.active?(:feature_name) #=> false
FEATURE_FLAGS.inactive?(:feature_name) #=> true

Activate a feature for a particular record/object

FEATURE_FLAGS.active_partially?(:feature_name) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

FEATURE_FLAGS.inactive_partially?(:feature_name) #=> false
FEATURE_FLAGS.inactive_for?(:feature_name, User.first) #=> true
FEATURE_FLAGS.inactive_for?(:feature_name, User.last) #=> true

FEATURE_FLAGS.activate_for(:feature_name, User.first) #=> true

FEATURE_FLAGS.active_partially?(:feature_name) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

FEATURE_FLAGS.inactive_partially?(:feature_name) #=> false
FEATURE_FLAGS.inactive_for?(:feature_name, User.first) #=> false
FEATURE_FLAGS.inactive_for?(:feature_name, User.last) #=> true

Note that the flag itself has to be active partially for any record/object specific settings to work. When the flag is deactivated it is completely turned off globally and for every specific record/object.

# The flag is deactivated in the global scope to begin with
FEATURE_FLAGS.active?(:feature_name) #=> false
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> false

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

# We activate it for the first User
FEATURE_FLAGS.activate_for(:feature_name, User.first)

FEATURE_FLAGS.active?(:feature_name) #=> false
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> false

# It is globally `deactivated` though, so the feature stays inactive for all users
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

# Once we activate the flag partially, record specific settings will be applied
FEATURE_FLAGS.activate_partially(:feature_name)

FEATURE_FLAGS.active?(:feature_name) #=> true
FEATURE_FLAGS.active_partially?(:feature_name) #=> true
FEATURE_FLAGS.active_globally?(:feature_name) #=> false

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

FEATURE_FLAGS.deactivate(:feature_name)

FEATURE_FLAGS.active?(:feature_name) #=> false
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> false

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

There is a convenience method activate_for!, which activates the feature partially and for specific records/objects at the same time

# The flag is deactivated in the global scope to begin with
FEATURE_FLAGS.active?(:feature_name) #=> false
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> false

# We activate it in the global scope and for the first User
FEATURE_FLAGS.activate_for!(:feature_name, User.first)

FEATURE_FLAGS.active?(:feature_name) #=> true
FEATURE_FLAGS.active_partially?(:feature_name) #=> true
FEATURE_FLAGS.active_globally?(:feature_name) #=> false

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

You can also pass an array of objects to activate all of them simultaneously

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
FEATURE_FLAGS.active_for?(:feature_name, User.find(2)) #=> false
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

FEATURE_FLAGS.activate_for(:feature_name, User.first(2))

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.find(2)) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

Activate the feature for every record

# The flag is active partially
FEATURE_FLAGS.active?(:feature_name) #=> true
FEATURE_FLAGS.active_partially?(:feature_name) #=> true
FEATURE_FLAGS.active_globally?(:feature_name) #=> false

# It is also enabled for the first user
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

# We force it onto every user
FEATURE_FLAGS.activate(:feature_name)

FEATURE_FLAGS.active?(:feature_name) #=> true
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> true

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> true

# We can easily return to the previous settings
FEATURE_FLAGS.activate_partially(:feature_name)

FEATURE_FLAGS.active?(:feature_name) #=> true
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> true

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false

Deactivate a feature for a particular record/object

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true

FEATURE_FLAGS.deactivate_for(:feature_name, User.first)

FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false

Run a block of code only when the flag is active

There are two ways of running code only when the feature flag is active

number = 1
if FEATURE_FLAGS.active?(:feature_name)
  number += 1
end

if FEATURE_FLAGS.inactive?(:feature_name)
  number += 1
end

# or using a block

# this code will run only when the :feature_name flag is active (either partially or globally)
FEATURE_FLAGS.when_active(:feature_name) do
  number += 1
end

# the opposite
FEATURE_FLAGS.when_inactive(:feature_name) do
  number += 1
end

# this code will run only when the :feature_name flag is active globally
FEATURE_FLAGS.when_active_globally(:feature_name) do
  number += 1
end

# the opposite
FEATURE_FLAGS.when_inactive_globally(:feature_name) do
  number += 1
end

# this code will run only when the :feature_name flag is active partially (only for specific records/users)
FEATURE_FLAGS.when_active_partially(:feature_name) do
  number += 1
end

# the opposite
FEATURE_FLAGS.when_inactive_partially(:feature_name) do
  number += 1
end

# this code will run only if the :feature_name flag is active for the first User
FEATURE_FLAGS.when_active_for(:feature_name, User.first) do
  number += 1
end

# the opposite
FEATURE_FLAGS.when_inactive_for(:feature_name, User.first) do
  number += 1
end

# feature flags that don't exist will return false
FEATURE_FLAGS.active?(:non_existant) #=> false
FEATURE_FLAGS.inactive?(:non_existant) #=> true

if FEATURE_FLAGS.active_for?(:feature_name, User.first)
  number += 1
end

if FEATURE_FLAGS.inactive_for?(:feature_name, User.first)
  number += 1
end

Adding feature flags

You can add new feature flags programmatically, though we highly encourage you to use the generated config/simple_feature_flags.yml file instead. It will make it easier to add and/or remove feature flags automatically on app startup without having to add them manually after merging a branch with new feature flags.

In case you'd like to add flags programmatically

FEATURE_FLAGS.add(:feature_name, 'Description')

FEATURE_FLAGS.active?(:feature_name) #=> false
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false

# add a new globally active flag
FEATURE_FLAGS.add(:active_feature, 'Description', :globally)

FEATURE_FLAGS.active?(:active_feature) #=> true
FEATURE_FLAGS.active_partially?(:active_feature) #=> false
FEATURE_FLAGS.active_globally?(:active_feature) #=> true
FEATURE_FLAGS.active_for?(:active_feature, User.first) #=> true

# add a new partially active flag
FEATURE_FLAGS.add(:feature_active_partially, 'Description', :partially)

FEATURE_FLAGS.active?(:feature_active_partially) #=> true
FEATURE_FLAGS.active_partially?(:feature_active_partially) #=> true
FEATURE_FLAGS.active_globally?(:feature_active_partially) #=> false
FEATURE_FLAGS.active_for?(:feature_active_partially, User.first) #=> false

Removing feature flags

You can remove feature flags programmatically, though we highly encourage you to use the generated config/simple_feature_flags.yml file instead. It will make it easier to add and/or remove feature flags automatically on app startup without having to add them manually after merging a branch with new feature flags.

In case you'd like to remove flags programmatically

FEATURE_FLAGS.remove(:feature_name)

FEATURE_FLAGS.active?(:feature_name) #=> false
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
FEATURE_FLAGS.active_globally?(:feature_name) #=> false

FEATURE_FLAGS.inactive?(:feature_name) #=> true
FEATURE_FLAGS.inactive_partially?(:feature_name) #=> true
FEATURE_FLAGS.inactive_globally?(:feature_name) #=> true

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/simple_feature_flags.

License

The gem is available as open source under the terms of the MIT License.

About

A Ruby gem which adds simple feature flag functionality.

Resources

License

Stars

Watchers

Forks

Packages

No packages published