Skip to content

Instantly share code, notes, and snippets.

@wojtha
Created December 6, 2016 16:47
Show Gist options
  • Save wojtha/43c68be62757d0b7030485efcf13583b to your computer and use it in GitHub Desktop.
Save wojtha/43c68be62757d0b7030485efcf13583b to your computer and use it in GitHub Desktop.
Feature flags in 20 LOC for ruby apps based on http://blog.arkency.com/2015/11/simple-feature-toggle-for-rails-app/
# Class FeatureFlag defines and stores feature flags
#
# @see http://blog.arkency.com/2015/11/simple-feature-toggle-for-rails-app/
#
class FeatureFlag
def initialize
@flags = {}
yield self if block_given?
end
def define(name, &block)
@flags[name] = block
end
def flags
@flags.keys
end
def with(name, *args, &block)
block.call if active?(name, *args)
end
def active?(name, *args)
@flags.fetch(name, proc{ |*_args| false }).call(*args)
end
def inactive?(name, *args)
!active?(name, *args)
end
end
require_relative 'feature_flag'
describe FeatureFlag do
describe '#initialize' do
it 'yields self to allow better setup' do
expect{ |b| described_class.new(&b) }.to yield_with_args(FeatureFlag)
end
it 'allows definition of features within the yielded block' do
features = described_class.new do |feature|
feature.define(:flag1)
feature.define('flag2')
end
expect( features.flags ).to have(2).items
expect( features.flags ).to include :flag1, 'flag2'
end
end
describe '#define' do
it 'defines flag' do
subject.define 'flag'
expect( subject.flags ).to include 'flag'
end
end
describe '#flags' do
context 'when no flag is defined' do
it 'returns empty array' do
expect( subject.flags ).to eq []
end
end
context 'when flags are defined' do
it 'contains defined flag names' do
subject.define(:flag1)
subject.define('flag2')
expect( subject.flags ).to have(2).items
expect( subject.flags ).to include :flag1, 'flag2'
end
end
end
describe '#active?' do
context 'when flag is truthy' do
it 'returns true' do
subject.define(:flag) { true }
expect( subject.active?(:flag) ).to eq true
end
end
context 'when flag is falsy' do
it 'returns false' do
subject.define(:flag) { false }
expect( subject.active?(:flag) ).to eq false
end
end
context 'when flag is not defined' do
it 'returns false' do
expect( subject.active?(:flag) ).to eq false
end
end
end
describe '#inactive?' do
context 'when flag is truthy' do
it 'returns false' do
subject.define(:flag) { true }
expect( subject.inactive?(:flag) ).to eq false
end
end
context 'when flag is falsy' do
it 'returns true' do
subject.define(:flag) { false }
expect( subject.inactive?(:flag) ).to eq true
end
end
context 'when flag is not defined' do
it 'returns true' do
expect( subject.inactive?(:flag) ).to eq true
end
end
end
describe '#with &block' do
context 'when flag is truthy' do
it 'calls the block' do
subject.define(:flag) { true }
probe = lambda{}
expect( probe ).to receive(:call)
subject.with(:flag, &probe)
end
end
context 'when flag is falsy' do
it 'does not call the block' do
subject.define(:flag) { false }
probe = lambda{}
expect( probe ).not_to receive(:call)
subject.with(:flag, &probe)
end
end
end
end
FEATURE = FeatureFlag.new do |feature|
feature.define(:new_user_profile) do |user_id:|
Admin.where(user_id: user_id).exists?
end
feature.define(:third_party_analytics) do
not Rails.env.production?
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment