featurer 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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