featurer 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9df68ddc8f5f914b1893afb7156d3a88d8b24697
4
- data.tar.gz: cb25475d7fb0d5e2ea0cdf697c26c9744d9197ce
3
+ metadata.gz: b841a0cb7f4bdda8b8484436e69c086ed3c356e8
4
+ data.tar.gz: b3cef4ae265b6de20e91b174923e676afaaa2dbf
5
5
  SHA512:
6
- metadata.gz: 85061688766dc09e391bfb439dcc945517a85bbc3e9711c9064c6ba530b999451df0ff3d6cf32e624e9c4eaced4c1c0b54c559abe2152ea0d5f9a3f648feb0d2
7
- data.tar.gz: 504147f604eb16472905c1c05c66ecb040841390a3c30d592d4b6df913c7cd6a77f59b26b0f2499bb22f711140797d23811a0b9b260559a3c57eaea707c25be3
6
+ metadata.gz: 77548e6ba64d74da3a3f94a309084469e9fe6fffdd9f1b4bdae38aa632bbc589f872de9bd66ccffe1be7e1d74dbdbc0c7a326b5305dba4da6a8ca713393db89f
7
+ data.tar.gz: 8ea8674ab337fbb44b1eccdd95da7d5cde70d22a251c871569143ad21834da0ea77b657d930d1d5dc3b27668ed7b83aca74bdb98dd54237d044ac4c9c6fd90d1
data/.gitignore CHANGED
@@ -11,4 +11,6 @@
11
11
  *.so
12
12
  *.o
13
13
  *.a
14
+ .byebug_history
14
15
  mkmf.log
16
+ vendor/bundle
data/.hound.yml ADDED
@@ -0,0 +1,2 @@
1
+ ruby:
2
+ config_file: .rubocop.yml
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --colour
2
2
  --require ./spec/support/fast_feedback_formatter
3
3
  --format documentation FastFeedbackFormatter
4
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.1
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ LineLength:
8
+ Max: 120
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.1.1
1
+ 2.3.1
data/.travis.yml CHANGED
@@ -1,11 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.0
6
- - 2.1.1
7
- - 2.1.2
8
- - 2.1.3
3
+ - 2.1.10
4
+ - 2.2.6
5
+ - 2.3.3
6
+ - 2.4.0
9
7
  services:
10
8
  - redis-server
11
9
  bundler_args: --without debug
data/Gemfile CHANGED
@@ -1,15 +1,18 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
 
3
4
  # Specify your gem's dependencies in featurer.gemspec
4
5
  gemspec
5
6
 
6
7
  group :development do
7
- gem 'rubocop', '~> 0.26'
8
- gem 'guard', '~> 2.6'
9
- gem 'guard-rspec', '~> 4.3'
8
+ gem 'rubocop', '~> 0.47.1'
9
+ end
10
+
11
+ group :test do
12
+ gem 'fakeredis', '~> 0.5.0'
10
13
  end
11
14
 
12
15
  group :debug do
13
- gem 'pry', '~> 0.10'
14
- gem 'pry-debugger', '~> 0.2'
16
+ gem 'pry', '~> 0.10.4'
17
+ gem 'pry-byebug', '~> 3.4.2'
15
18
  end
data/Guardfile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # A sample Guardfile
2
3
  # More info at https://github.com/guard/guard#readme
3
4
 
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'bundler/gem_tasks'
2
3
 
3
4
  require 'rspec/core/rake_task'
@@ -6,4 +7,4 @@ RSpec::Core::RakeTask.new(:spec) do |task|
6
7
  task.rspec_opts = ['--color', '--format', 'documentation']
7
8
  end
8
9
 
9
- task :default => :spec
10
+ task default: :spec
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FeaturerHelper
4
+ def featurer_head_tag(value)
5
+ render partial: 'featurer/head_tag', locals: { value: value }
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ <script>
2
+ var Featurer = {
3
+ enabledFeatures: <%= Featurer.enabled_features(value).to_json.html_safe %>,
4
+ isEnabled: function(feature_name) {
5
+ return this.enabledFeatures.indexOf(feature_name) != -1
6
+ }
7
+ };
8
+ </script>
data/featurer.gemspec CHANGED
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'featurer/version'
@@ -8,8 +9,8 @@ Gem::Specification.new do |spec|
8
9
  spec.version = Featurer::VERSION
9
10
  spec.authors = ['Alejandro El Informatico']
10
11
  spec.email = ['aeinformatico@gmail.com']
11
- spec.summary = %q{Easy feature flag for your project}
12
- spec.description = %q{Easy feature flag for your project shipped with Redis support}
12
+ spec.summary = 'Easy feature flag for your project'
13
+ spec.description = 'Easy feature flag for your project shipped with Redis support'
13
14
  spec.homepage = 'https://github.com/ainformatico/featurer'
14
15
  spec.license = 'MIT'
15
16
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Featurer
2
3
  class Adapter
3
4
  attr_reader :config
@@ -10,20 +11,55 @@ module Featurer
10
11
  @config = config
11
12
  end
12
13
 
13
- def delete(feature)
14
- fail 'implement a delete method'
14
+ # Attaches a new matching value to the given feature.
15
+ # If the feature doesn't exist, the adapter should create it automatically.
16
+ # Feature matching_values must be matched in the order they were added.
17
+ #
18
+ # @param _feature the name of the feature as will be used when questioning if the feature is #on?
19
+ # @param _matching_value the value that will be used to match when calling #on?. Valid values are:
20
+ # => - true: it will match any value (ie: the feature enabled globally)
21
+ # => - false: the feature is disabled globally
22
+ # => - regular expression: matched when calling #on?, if regular expression is passed
23
+ # => - other types: matched literally when calling #on?, based on the #to_s representation
24
+ def add(_feature, _matching_value)
25
+ raise NotImplementedError
15
26
  end
16
27
 
17
- def key(name)
18
- fail 'implement a key method'
28
+ # Completely removes a feature from the system.
29
+ # If the feature doesn't exist, the adapter should not fail.
30
+ def delete(_feature)
31
+ raise NotImplementedError
19
32
  end
20
33
 
21
- def on?(feature, user_id = nil)
22
- fail 'implement a on? method'
34
+ # Returns true if the feature has a matching value attached wich matches the given value.
35
+ #
36
+ # @param _feature the feature name, as provided when calling #add or #register
37
+ # @param _value the value that will be used when matching against the stored matching_value for the given feature
38
+ # @return a Boolean that depends on the _value provided
39
+ # => - true if _value is nil and the _feature has a matching_value of true
40
+ # => - true if _value is an integer and the _feature has that exact integer attached as a matching_value
41
+ # => - true if _value is a string and the _feature has a matching_value which is a regexp that matches _value
42
+ # => - true if _value is any other type and the _feature has a matching_value which has the same exact #to_s
43
+ # => representation
44
+ # => - false in any other case
45
+ def on?(_feature, _value = true)
46
+ raise NotImplementedError
23
47
  end
24
48
 
25
- def register(name, value = true)
26
- fail 'implement a register? method'
49
+ # Returns an array with all the enabled features that match the given value (or just the global ones if no value is
50
+ # provided)
51
+ #
52
+ # @param _value the value that will be used when matching against the stored matching_values
53
+ def enabled_features(_value = true)
54
+ raise NotImplementedError
55
+ end
56
+
57
+ # First deletes the given _feature and then creates it again with only the provided _matching_value attached.
58
+ #
59
+ # @param _feature the feature to register
60
+ # @param _matching_value the new matching_value for the feature that replaces any existing matching_value on it
61
+ def register(_feature, _matching_value = true)
62
+ raise NotImplementedError
27
63
  end
28
64
  end
29
65
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Featurer
2
3
  class AdapterManager
3
4
  class << self
@@ -20,9 +21,9 @@ module Featurer
20
21
 
21
22
  def extract_name(klass)
22
23
  klass.name
23
- .downcase[/(?:\w+$)/] # get only class name
24
- .sub('adapter', '') # remove adapter prefix
25
- .to_sym
24
+ .downcase[/(?:\w+$)/] # get only class name
25
+ .sub('adapter', '') # remove adapter prefix
26
+ .to_sym
26
27
  end
27
28
  end
28
29
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Featurer
2
3
  class AdapterProxy
3
4
  attr_reader :adapter
@@ -1,35 +1,53 @@
1
+ # frozen_string_literal: true
1
2
  require 'redis'
2
3
 
3
4
  module Featurer
4
5
  class RedisAdapter < Adapter
5
6
  def prepare
6
- @redis = ::Redis.new({
7
- host: @config[:host],
8
- port: @config[:port],
9
- db: @config[:db]
10
- })
11
- end
12
-
13
- def delete(feature)
14
- delete_key(feature)
7
+ @redis = @config[:client] || ::Redis.new(host: @config[:host],
8
+ port: @config[:port],
9
+ db: @config[:db])
15
10
  end
16
11
 
17
12
  def add(feature, value)
18
13
  save_set(feature, value)
19
14
  end
20
15
 
16
+ def delete(feature)
17
+ delete_key(feature)
18
+ end
19
+
21
20
  def on?(feature, value = true)
22
21
  fetch_from_set(feature, value)
22
+ rescue => e
23
+ @config[:logger].warn e
24
+ false
25
+ end
26
+
27
+ def enabled_features(value = true)
28
+ all_features.select { |feature| on?(feature, value) }
29
+ rescue => e
30
+ @config[:logger].warn e
31
+ []
23
32
  end
24
33
 
25
34
  def off(feature, value)
26
35
  remove_from_set(feature, value)
27
36
  end
28
37
 
29
- def register(name, value = true)
38
+ def register(feature, value = true)
30
39
  # ensure old data is wiped
31
- delete(name)
32
- save_set(name, value)
40
+ delete(feature)
41
+ save_set(feature, value)
42
+ end
43
+
44
+ def feature_list
45
+ full_feature_names.map do |full_feature_name|
46
+ {
47
+ name: short_feature_name(full_feature_name),
48
+ matching_values: @redis.smembers(full_feature_name)
49
+ }
50
+ end
33
51
  end
34
52
 
35
53
  private
@@ -46,12 +64,38 @@ module Featurer
46
64
  @redis.sadd key(name), value
47
65
  end
48
66
 
49
- def fetch_from_set(name, id)
50
- @redis.sismember(key(name), id)
67
+ def fetch_from_set(name, value)
68
+ matching_values = @redis.smembers(key(name))
69
+
70
+ matching_values.each do |matching_value|
71
+ return true if matching_value == 'true' # Globally enabled feature
72
+
73
+ if value.is_a?(String) && matching_value =~ /\(\?-mix:.+\)/ # Regexp matching
74
+ return true if Regexp.new(matching_value).match(value)
75
+ elsif matching_value == value.to_s # By exact value
76
+ return true
77
+ end
78
+ end
79
+
80
+ false
51
81
  end
52
82
 
53
83
  def remove_from_set(name, id)
54
84
  @redis.srem(key(name), id)
55
85
  end
86
+
87
+ def all_features
88
+ full_feature_names.map do |key|
89
+ short_feature_name(key)
90
+ end
91
+ end
92
+
93
+ def full_feature_names
94
+ @redis.keys("#{@config[:prefix]}:feature:*")
95
+ end
96
+
97
+ def short_feature_name(full_feature_name)
98
+ full_feature_name.split("#{@config[:prefix]}:feature:").last.to_sym
99
+ end
56
100
  end
57
101
  end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+
1
4
  module Featurer
2
5
  module Facade
3
- attr_accessor :adapter
6
+ attr_accessor :adapter, :logger
4
7
 
5
8
  def configure(config)
9
+ @logger = config[:logger] ||= Logger.new(STDOUT)
6
10
  @adapter = AdapterProxy.new(config).adapter
7
11
  end
8
12
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Featurer
2
- VERSION = '0.0.4'
4
+ VERSION = '0.1.0'.freeze
3
5
  end
data/lib/featurer.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'featurer/adapter_proxy'
2
3
  require 'featurer/adapter_manager'
3
4
  require 'featurer/adapter'
@@ -7,4 +8,12 @@ require 'featurer/version'
7
8
 
8
9
  module Featurer
9
10
  extend Facade
11
+
12
+ if const_defined?(:Rails)
13
+ class Engine < ::Rails::Engine
14
+ initializer 'featurer:init_adapter' do
15
+ Featurer.init! unless Featurer.adapter
16
+ end
17
+ end
18
+ end
10
19
  end
@@ -1,7 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Featurer::AdapterManager do
4
-
5
5
  describe 'registered adapters' do
6
6
  it 'checks the default adapter' do
7
7
  expect(Featurer::AdapterManager.adapters).to include(redis: Featurer::RedisAdapter)
@@ -1,7 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Featurer::AdapterProxy do
4
-
5
5
  describe 'default adapter' do
6
6
  it 'creates the default adapter' do
7
7
  proxy = Featurer::AdapterProxy.new
@@ -9,11 +9,11 @@ describe Featurer::AdapterProxy do
9
9
  end
10
10
 
11
11
  it 'creates a new adapter' do
12
- class TestAdapter < Featurer::Adapter
12
+ class ProxyAdapter < Featurer::Adapter
13
13
  end
14
14
 
15
- proxy = Featurer::AdapterProxy.new(adapter: :test)
16
- expect(proxy.adapter).to be_an(TestAdapter)
15
+ proxy = Featurer::AdapterProxy.new(adapter: :proxy)
16
+ expect(proxy.adapter).to be_an(ProxyAdapter)
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Featurer::Adapter do
4
+ describe '#add' do
5
+ it 'raises an error' do
6
+ expect { subject.add(:feature, :matching_value) }.to raise_error(NotImplementedError)
7
+ end
8
+ end
9
+
10
+ describe '#delete' do
11
+ it 'raises an error' do
12
+ expect { subject.delete(:feature) }.to raise_error(NotImplementedError)
13
+ end
14
+ end
15
+
16
+ describe '#on?' do
17
+ it 'raises an error' do
18
+ expect { subject.on?(:feature, :value) }.to raise_error(NotImplementedError)
19
+ end
20
+ end
21
+
22
+ describe '#register' do
23
+ it 'raises an error' do
24
+ expect { subject.register(:feature, :matching_value) }.to raise_error(NotImplementedError)
25
+ end
26
+ end
27
+ end
@@ -1,14 +1,78 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Featurer::RedisAdapter do
4
- it 'should create the connection' do
5
- expect_any_instance_of(Featurer::RedisAdapter).to receive(:prepare)
6
- .and_call_original
5
+ let(:redis_handler) { Featurer.adapter.instance_variable_get(:@redis) }
7
6
 
8
- Featurer.init
9
- redis_handler = Featurer.adapter.instance_variable_get(:@redis)
7
+ before { Featurer.init! }
10
8
 
9
+ it 'creates the connection' do
11
10
  expect(redis_handler).to be_an(Redis)
12
11
  expect(redis_handler.ping).to eq('PONG')
13
12
  end
13
+
14
+ describe '#on?' do
15
+ context 'when there is an exception' do
16
+ let(:exception) { StandardError.new }
17
+ let(:logger) { double(Logger) }
18
+
19
+ before do
20
+ subject.instance_variable_set :@config, logger: logger
21
+
22
+ expect(logger).to receive(:warn)
23
+ expect(subject).to receive(:fetch_from_set).and_raise(exception)
24
+ end
25
+
26
+ it "doesn't propagate the exception, just logs it" do
27
+ expect(subject.on?(:feature)).to be(false)
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '#enabled_features' do
33
+ context 'when there is an exception' do
34
+ let(:exception) { StandardError.new }
35
+ let(:logger) { double(Logger) }
36
+
37
+ before do
38
+ subject.instance_variable_set :@config, logger: logger
39
+
40
+ expect(logger).to receive(:warn)
41
+ expect(subject).to receive(:all_features).and_raise(exception)
42
+ end
43
+
44
+ it "doesn't propagate the exception, just logs it" do
45
+ expect(subject.enabled_features(:feature)).to eq([])
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '#prepare' do
51
+ subject { described_class.new(config).prepare }
52
+
53
+ context 'a preconfigured client is provided' do
54
+ let(:client) { double(Redis) }
55
+ let(:config) { { client: client } }
56
+
57
+ it 'sets the given client as the adapter client' do
58
+ expect(subject).to eq(client)
59
+ end
60
+ end
61
+
62
+ context 'the connection details are provided' do
63
+ let(:config) do
64
+ {
65
+ host: 'the_host',
66
+ port: 3425,
67
+ db: 89
68
+ }
69
+ end
70
+
71
+ it 'creates a new redis client with the given settings' do
72
+ expect(subject.client.host).to eq(config[:host])
73
+ expect(subject.client.port).to eq(config[:port])
74
+ expect(subject.client.db).to eq(config[:db])
75
+ end
76
+ end
77
+ end
14
78
  end
@@ -1,20 +1,52 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Featurer::Facade do
4
5
  describe 'configuration' do
5
- it 'should use default options' do
6
+ it 'uses default options' do
6
7
  Featurer.init!
8
+
7
9
  expect(Featurer.adapter.config).to include(adapter: :redis)
8
10
  end
9
11
 
10
- it 'should configure the adapter' do
11
- options = {
12
- adapter: :redis,
13
- prefix: :custom,
14
- host: '192.168.1.1'
15
- }
16
- Featurer.configure(options)
17
- expect(Featurer.adapter.config).to include(options)
12
+ context 'when providing a configuration' do
13
+ let(:new_config) do
14
+ {
15
+ adapter: :redis,
16
+ prefix: :custom,
17
+ host: '192.168.1.1'
18
+ }
19
+ end
20
+
21
+ it 'configures the adapter' do
22
+ Featurer.configure(new_config)
23
+
24
+ expect(Featurer.adapter.config).to include(new_config)
25
+ end
26
+ end
27
+
28
+ describe 'supports passing a custom logger in the config' do
29
+ let(:logger) { double(Logger) }
30
+
31
+ context 'no logger is passed in the config' do
32
+ before do
33
+ expect(Logger).to receive(:new).with(STDOUT).and_return(logger)
34
+ end
35
+
36
+ it 'sets a default logger' do
37
+ Featurer.configure({})
38
+
39
+ expect(Featurer.logger).to eq(logger)
40
+ end
41
+ end
42
+
43
+ context 'a logger is passed in the config' do
44
+ it 'sets a default logger' do
45
+ Featurer.configure(logger: logger)
46
+
47
+ expect(Featurer.logger).to eq(logger)
48
+ end
49
+ end
18
50
  end
19
51
  end
20
52
 
@@ -25,23 +57,23 @@ describe Featurer::Facade do
25
57
  end
26
58
 
27
59
  describe '#init' do
28
- it 'should reset the adapter' do
60
+ it 'resets the adapter' do
29
61
  Featurer.configure(adapter: :nil)
30
62
  Featurer.init!
31
63
 
32
64
  expect(Featurer.config).to include(adapter: :redis)
33
65
  end
34
66
 
35
- it 'should not reset the adapter' do
67
+ it "doesn't reset the adapter" do
36
68
  Featurer.configure(adapter: :nil, prefix: :custom)
37
69
  Featurer.init
38
70
 
39
- expect(Featurer.config).to eq(adapter: :nil, prefix: :custom)
71
+ expect(Featurer.config).to include(adapter: :nil, prefix: :custom)
40
72
  end
41
73
  end
42
74
 
43
75
  describe '#reset' do
44
- it 'should reset the adapter' do
76
+ it 'resets the adapter' do
45
77
  Featurer.configure(adapter: :nil)
46
78
  Featurer.reset
47
79
 
@@ -49,4 +81,10 @@ describe Featurer::Facade do
49
81
  end
50
82
  end
51
83
  end
84
+
85
+ describe 'calling methods not defined in the adapter' do
86
+ it 'produces an error' do
87
+ expect { Featurer.this_is_clearly_not_a_valid_method_ok }.to raise_error(NoMethodError)
88
+ end
89
+ end
52
90
  end
@@ -1,104 +1,174 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe Featurer do
4
- describe 'feature flag is defined' do
5
- after(:each) { Featurer.delete(:feature) }
5
+ before { Featurer.init! }
6
6
 
7
+ let(:user_id) { 1 }
8
+
9
+ describe '#enabled_features' do
10
+ before do
11
+ Featurer.register(:feature_1)
12
+ Featurer.register(:feature_2, [user_id])
13
+ Featurer.register(:feature_3, [user_id + 1, /client_123/])
14
+ Featurer.register(:feature_4, /^admin_/)
15
+ end
16
+
17
+ it 'returns all enabled features' do
18
+ expect(Featurer.enabled_features.to_set).to eq([:feature_1].to_set)
19
+ expect(Featurer.enabled_features(user_id).to_set).to eq([:feature_1, :feature_2].to_set)
20
+ expect(Featurer.enabled_features('admin_client_123').to_set).to eq([:feature_1, :feature_3, :feature_4].to_set)
21
+ end
22
+ end
23
+
24
+ context 'globally enabled feature' do
7
25
  before do
8
- Featurer.init!
26
+ Featurer.register :feature, true
27
+ end
28
+
29
+ it 'matches on an integer' do
30
+ expect(Featurer.on?(:feature, 1)).to be
31
+ end
32
+
33
+ it 'matches on a boolean' do
34
+ expect(Featurer.on?(:feature, true)).to be
35
+ end
36
+
37
+ it 'matches on a string' do
38
+ expect(Featurer.on?(:feature, 'admin_client_123')).to be
39
+ end
40
+ end
41
+
42
+ context 'feature matching_value is a list of integers' do
43
+ it 'returns true because user is in the list' do
44
+ Featurer.register :feature, [user_id]
45
+
46
+ expect(Featurer.on?(:feature, user_id)).to be
47
+ end
48
+
49
+ it 'defines the user twice and returns true' do
50
+ Featurer.register :feature, [user_id]
51
+ Featurer.register :feature, [user_id]
52
+
53
+ expect(Featurer.on?(:feature, user_id)).to be
54
+ end
55
+
56
+ it 'returns false because user is not in the list' do
57
+ Featurer.register :feature, [user_id + 1]
58
+
59
+ expect(Featurer.on?(:feature, user_id)).to_not be
60
+ end
61
+
62
+ it 'gives priority to global features' do
63
+ Featurer.register :feature, [user_id]
64
+ Featurer.register :feature, true
65
+
66
+ expect(Featurer.on?(:feature)).to be
67
+ expect(Featurer.on?(:feature, user_id + 1)).to be
9
68
  end
10
69
 
11
- let(:user_id) { 1 }
70
+ it 'removes a single user from the feature' do
71
+ Featurer.register :feature, [user_id, user_id + 1]
72
+ Featurer.off :feature, user_id
12
73
 
13
- describe 'per user' do
14
- it 'returns true because user is in the list' do
15
- Featurer.register :feature, [user_id]
16
- expect(Featurer.on?(:feature, user_id)).to be
17
- end
74
+ expect(Featurer.on?(:feature, user_id)).to_not be
75
+ expect(Featurer.on?(:feature, user_id + 1)).to be
76
+ end
18
77
 
19
- it 'defines the user twice and returns true' do
20
- Featurer.register :feature, [user_id]
21
- Featurer.register :feature, [user_id]
22
- expect(Featurer.on?(:feature, user_id)).to be
23
- end
78
+ it 'removes multiple users from the feature' do
79
+ Featurer.register :feature, [user_id, user_id + 1]
80
+ Featurer.off :feature, [user_id, user_id + 1]
24
81
 
25
- it 'returns false because user is not in the list' do
26
- Featurer.register :feature, [user_id + 1]
27
- expect(Featurer.on?(:feature, user_id)).to_not be
28
- end
82
+ expect(Featurer.on?(:feature, user_id)).to_not be
83
+ expect(Featurer.on?(:feature, user_id + 1)).to_not be
84
+ end
29
85
 
30
- it 'converts a per user feature into a global one' do
31
- Featurer.register :feature, [user_id]
32
- Featurer.register :feature, true
86
+ it 'adds a user to a non-existing feature' do
87
+ Featurer.add :feature, user_id
33
88
 
34
- expect(Featurer.on?(:feature)).to be
35
- expect(Featurer.on?(:feature, user_id)).to_not be
36
- end
89
+ expect(Featurer.on?(:feature, user_id)).to be
90
+ end
37
91
 
38
- it 'should remove a single user from the feature' do
39
- Featurer.register :feature, [user_id, user_id + 1]
40
- Featurer.off :feature, user_id
92
+ it 'adds a single user to a existing feature' do
93
+ Featurer.register :feature, [user_id]
94
+ Featurer.add :feature, user_id + 1
41
95
 
42
- expect(Featurer.on?(:feature, user_id)).to_not be
43
- expect(Featurer.on?(:feature, user_id + 1)).to be
44
- end
96
+ expect(Featurer.on?(:feature, user_id)).to be
97
+ expect(Featurer.on?(:feature, user_id + 1)).to be
98
+ end
45
99
 
46
- it 'should remove a multiple users from the feature' do
47
- Featurer.register :feature, [user_id, user_id + 1]
100
+ it 'adda multiple users to a feature' do
101
+ Featurer.register :feature, [user_id]
102
+ Featurer.add :feature, [user_id + 1, user_id + 2]
48
103
 
49
- Featurer.off :feature, [user_id, user_id + 1]
50
- expect(Featurer.on?(:feature, user_id)).to_not be
51
- expect(Featurer.on?(:feature, user_id + 1)).to_not be
52
- end
104
+ expect(Featurer.on?(:feature, user_id)).to be
105
+ expect(Featurer.on?(:feature, user_id + 1)).to be
106
+ expect(Featurer.on?(:feature, user_id + 2)).to be
107
+ end
53
108
 
54
- it 'should add a user to a non-existing feature' do
55
- Featurer.add :feature, user_id
109
+ it "doesn't match a string" do
110
+ Featurer.register :feature, [user_id]
56
111
 
57
- expect(Featurer.on?(:feature, user_id)).to be
58
- end
112
+ expect(Featurer.on?(:feature, user_id.to_s))
113
+ end
114
+ end
59
115
 
60
- it 'should add a single user to a feature' do
61
- Featurer.register :feature, [user_id]
62
- Featurer.add :feature, user_id + 1
116
+ context 'feature matching_value is a regular expressions' do
117
+ it 'matches a matching string' do
118
+ Featurer.register :feature, [/^admin_/]
63
119
 
64
- expect(Featurer.on?(:feature, user_id)).to be
65
- expect(Featurer.on?(:feature, user_id + 1)).to be
66
- end
120
+ expect(Featurer.on?(:feature, 'admin_1234')).to be(true)
121
+ end
67
122
 
68
- it 'should add multiple users to a feature' do
69
- Featurer.register :feature, [user_id]
70
- Featurer.add :feature, [user_id + 1, user_id + 2]
123
+ it "doesn't match a non-matching string" do
124
+ Featurer.register :feature, [/^admin_/]
71
125
 
72
- expect(Featurer.on?(:feature, user_id)).to be
73
- expect(Featurer.on?(:feature, user_id + 1)).to be
74
- expect(Featurer.on?(:feature, user_id + 2)).to be
75
- end
126
+ expect(Featurer.on?(:feature, '_admin_1234')).to be(false)
76
127
  end
128
+ end
77
129
 
78
- describe 'global' do
79
- it 'returns true because the feature is enabled(default)' do
80
- Featurer.register :feature
81
- expect(Featurer.on?(:feature)).to be
82
- end
130
+ context 'feature matching_value is a Boolean' do
131
+ it 'returns true because the feature is enabled(default)' do
132
+ Featurer.register :feature
133
+ expect(Featurer.on?(:feature)).to be
134
+ end
83
135
 
84
- it 'returns true because the feature is enabled(manually)' do
85
- Featurer.register :feature, true
86
- expect(Featurer.on?(:feature)).to be
87
- end
136
+ it 'returns true because the feature is enabled(manually)' do
137
+ Featurer.register :feature, true
138
+ expect(Featurer.on?(:feature)).to be
139
+ end
88
140
 
89
- it 'returns false because the feature is disabled' do
90
- Featurer.register :feature, false
91
- expect(Featurer.on?(:feature)).to_not be
92
- end
141
+ it 'returns false because the feature is disabled' do
142
+ Featurer.register :feature, false
143
+ expect(Featurer.on?(:feature)).to_not be
144
+ end
93
145
 
94
- it 'converts a global feature into a per user one' do
95
- Featurer.register :feature, true
96
- Featurer.register :feature, [user_id]
146
+ it 'converts a global feature into a per user one' do
147
+ Featurer.register :feature, true
148
+ Featurer.register :feature, [user_id]
97
149
 
98
- expect(Featurer.on?(:feature, user_id)).to be
99
- expect(Featurer.on?(:feature)).to_not be
100
- end
150
+ expect(Featurer.on?(:feature, user_id)).to be
151
+ expect(Featurer.on?(:feature)).to_not be
101
152
  end
102
153
  end
103
154
 
155
+ context 'feature matching_value is a String' do
156
+ let(:value) { 'this is the value' }
157
+
158
+ before do
159
+ Featurer.register :feature, value
160
+ end
161
+
162
+ it 'matches on the exact value of the string' do
163
+ expect(Featurer.on?(:feature, value)).to be
164
+ end
165
+
166
+ it "doesn't match partially" do
167
+ expect(Featurer.on?(:feature, value[0..-3])).to_not be
168
+ end
169
+
170
+ it "doesn't match on an empty string" do
171
+ expect(Featurer.on?(:feature, '')).to_not be
172
+ end
173
+ end
104
174
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fakeredis/rspec'
4
+
1
5
  require 'featurer'
2
6
 
3
7
  # Requires supporting ruby files with custom matchers and macros, etc,
@@ -1,12 +1,17 @@
1
- require "rspec/core/formatters/progress_formatter"
1
+ # frozen_string_literal: true
2
+ require 'rspec/core/formatters/progress_formatter'
2
3
 
3
4
  class FastFeedbackFormatter < RSpec::Core::Formatters::ProgressFormatter
4
5
  def example_failed(example)
5
6
  super(example)
6
7
 
7
- example_group = example.metadata[:example_group]
8
+ print_formatted_example(example.metadata[:example_group])
9
+ end
10
+
11
+ private
8
12
 
9
- output.puts ""
13
+ def print_formatted_example(example_group)
14
+ output.puts ''
10
15
  output.puts red("#{example_group[:file_path]}:#{example_group[:line_number]}")
11
16
  output.puts red(example.execution_result[:exception].to_s.strip)
12
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: featurer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alejandro El Informatico
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-26 00:00:00.000000000 Z
11
+ date: 2017-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -74,7 +74,9 @@ extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
76
  - ".gitignore"
77
+ - ".hound.yml"
77
78
  - ".rspec"
79
+ - ".rubocop.yml"
78
80
  - ".ruby-version"
79
81
  - ".travis.yml"
80
82
  - Gemfile
@@ -82,6 +84,8 @@ files:
82
84
  - LICENSE.txt
83
85
  - README.md
84
86
  - Rakefile
87
+ - app/helpers/featurer_helper.rb
88
+ - app/views/featurer/_head_tag.html.erb
85
89
  - featurer.gemspec
86
90
  - lib/featurer.rb
87
91
  - lib/featurer/adapter.rb
@@ -92,6 +96,7 @@ files:
92
96
  - lib/featurer/version.rb
93
97
  - spec/lib/featurer/adapter_manager_spec.rb
94
98
  - spec/lib/featurer/adapter_proxy_spec.rb
99
+ - spec/lib/featurer/adapter_spec.rb
95
100
  - spec/lib/featurer/adapters/redis_spec.rb
96
101
  - spec/lib/featurer/facade_spec.rb
97
102
  - spec/lib/featurer_spec.rb
@@ -117,13 +122,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
122
  version: '0'
118
123
  requirements: []
119
124
  rubyforge_project:
120
- rubygems_version: 2.2.2
125
+ rubygems_version: 2.5.1
121
126
  signing_key:
122
127
  specification_version: 4
123
128
  summary: Easy feature flag for your project
124
129
  test_files:
125
130
  - spec/lib/featurer/adapter_manager_spec.rb
126
131
  - spec/lib/featurer/adapter_proxy_spec.rb
132
+ - spec/lib/featurer/adapter_spec.rb
127
133
  - spec/lib/featurer/adapters/redis_spec.rb
128
134
  - spec/lib/featurer/facade_spec.rb
129
135
  - spec/lib/featurer_spec.rb