flip_fab 1.0.0 → 1.0.16
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 +5 -5
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/dobby-actions.yml +29 -0
- data/.github/workflows/gem-publish.yml +46 -0
- data/.github/workflows/version-forget-me-not.yml +19 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +31 -0
- data/.rubocop_todo.yml +58 -0
- data/.ruby-version +1 -0
- data/.semaphore/semaphore.yml +49 -0
- data/CODEOWNERS +3 -0
- data/Gemfile +0 -12
- data/README.md +8 -2
- data/_pipeline/step_build_gem.sh +5 -0
- data/_pipeline/step_test_gem.sh +6 -0
- data/catalog-info.yaml +8 -0
- data/example/rails_app/Gemfile +4 -21
- data/example/rails_app/Rakefile +1 -1
- data/example/rails_app/app/assets/config/manifest.js +3 -0
- data/example/rails_app/app/assets/javascripts/application.js +0 -1
- data/example/rails_app/app/controllers/beavers_controller.rb +12 -13
- data/example/rails_app/bin/bundle +1 -1
- data/example/rails_app/bin/rails +1 -1
- data/example/rails_app/config/application.rb +2 -2
- data/example/rails_app/config/boot.rb +1 -1
- data/example/rails_app/config/environment.rb +1 -1
- data/example/rails_app/config/environments/development.rb +1 -1
- data/example/rails_app/config/environments/test.rb +2 -2
- data/example/rails_app/config/initializers/cookies_serializer.rb +1 -1
- data/example/rails_app/config.ru +1 -1
- data/example/rails_app/db/schema.rb +5 -8
- data/example/rails_app/spec/rails_helper.rb +2 -2
- data/example/rails_app/spec/spec_helper.rb +0 -1
- data/example/rails_app/test/controllers/beavers_controller_test.rb +12 -12
- data/example/rails_app/test/test_helper.rb +1 -1
- data/flip_fab.gemspec +11 -5
- data/lib/flip_fab/contextual_feature.rb +23 -19
- data/lib/flip_fab/cookie_persistence.rb +10 -17
- data/lib/flip_fab/feature.rb +2 -3
- data/lib/flip_fab/features_by_name.rb +5 -4
- data/lib/flip_fab/helper.rb +0 -1
- data/lib/flip_fab/persistence.rb +2 -3
- data/lib/flip_fab/version.rb +6 -1
- data/lib/flip_fab.rb +4 -4
- data/script/cibuild +10 -0
- data/spec/lib/flip_fab/contextual_feature_spec.rb +54 -57
- data/spec/lib/flip_fab/cookie_persistence.feature +3 -3
- data/spec/lib/flip_fab/cookie_persistence_spec.rb +36 -43
- data/spec/lib/flip_fab/feature_spec.rb +6 -9
- data/spec/lib/flip_fab/features_by_name_spec.rb +3 -6
- data/spec/lib/flip_fab/helper_spec.rb +35 -38
- data/spec/lib/flip_fab/persistence_spec.rb +2 -5
- data/spec/lib/flip_fab_spec.rb +11 -15
- data/spec/spec_helper.rb +47 -49
- data/spec/support/test_app.rb +2 -2
- data/spec/support/test_context.rb +1 -1
- data/spec/support/test_multiple_persistence.rb +2 -3
- data/spec/support/test_persistence.rb +2 -3
- data/spec/support/test_rack_context.rb +3 -3
- metadata +103 -11
- data/Gemfile.lock +0 -92
- data/example/rails_app/Gemfile.lock +0 -178
- data/example/rails_app/README.rdoc +0 -28
- data/example/rails_app/config/rabbit_feed.yml +0 -8
- data/example/rails_app/config/unicorn.rb +0 -4
    
        data/flip_fab.gemspec
    CHANGED
    
    | @@ -1,5 +1,4 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            lib = File.expand_path('../lib', __FILE__)
         | 
| 1 | 
            +
            lib = File.expand_path('lib', __dir__)
         | 
| 3 2 | 
             
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 3 | 
             
            require 'flip_fab/version'
         | 
| 5 4 |  | 
| @@ -8,13 +7,20 @@ Gem::Specification.new do |spec| | |
| 8 7 | 
             
              spec.version       = FlipFab::VERSION
         | 
| 9 8 | 
             
              spec.authors       = ['Simply Business']
         | 
| 10 9 | 
             
              spec.email         = ['tech@simplybusiness.co.uk']
         | 
| 11 | 
            -
              spec.description   =  | 
| 12 | 
            -
              spec.summary       =  | 
| 10 | 
            +
              spec.description   = 'A gem providing persistent, per-user feature flipping to Rack applications.'
         | 
| 11 | 
            +
              spec.summary       = 'A gem providing persistent, per-user feature flipping to Rack applications.'
         | 
| 13 12 | 
             
              spec.homepage      = 'https://github.com/simplybusiness/flip_fab'
         | 
| 14 13 | 
             
              spec.license       = 'MIT'
         | 
| 15 14 |  | 
| 16 | 
            -
              spec.files         = `git ls-files`.split( | 
| 15 | 
            +
              spec.files         = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
         | 
| 17 16 | 
             
              spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 18 17 | 
             
              spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 19 18 | 
             
              spec.require_paths = ['lib']
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              spec.add_development_dependency 'rack', '>=2.1.1'
         | 
| 21 | 
            +
              spec.add_development_dependency 'rack-test'
         | 
| 22 | 
            +
              spec.add_development_dependency 'rspec', '~> 3.5'
         | 
| 23 | 
            +
              spec.add_development_dependency 'rutabaga', '~> 3.0'
         | 
| 24 | 
            +
              spec.add_development_dependency 'simplycop', '~> 1.9'
         | 
| 25 | 
            +
              spec.add_development_dependency 'timecop', '~> 0.8'
         | 
| 20 26 | 
             
            end
         | 
| @@ -2,13 +2,19 @@ module FlipFab | |
| 2 2 | 
             
              class ContextualFeature
         | 
| 3 3 | 
             
                attr_reader :feature, :context
         | 
| 4 4 |  | 
| 5 | 
            -
                def initialize | 
| 5 | 
            +
                def initialize(feature, context)
         | 
| 6 6 | 
             
                  @feature = feature
         | 
| 7 7 | 
             
                  @context = context
         | 
| 8 | 
            -
                   | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
                   | 
| 8 | 
            +
                  return unless overridden?
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  @state = override
         | 
| 11 | 
            +
                  persist
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def as_json(options = {})
         | 
| 15 | 
            +
                  {
         | 
| 16 | 
            +
                    'feature' => feature.as_json(options)
         | 
| 17 | 
            +
                  }
         | 
| 12 18 | 
             
                end
         | 
| 13 19 |  | 
| 14 20 | 
             
                def enabled?
         | 
| @@ -27,16 +33,16 @@ module FlipFab | |
| 27 33 | 
             
                  self.state = :disabled
         | 
| 28 34 | 
             
                end
         | 
| 29 35 |  | 
| 30 | 
            -
                def state= | 
| 31 | 
            -
                  raise "Invalid state provided: `#{value}`, possible states are :enabled, :disabled" unless %i | 
| 32 | 
            -
                   | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
                   | 
| 36 | 
            +
                def state=(value)
         | 
| 37 | 
            +
                  raise "Invalid state provided: `#{value}`, possible states are :enabled, :disabled" unless %i[enabled disabled].include? value
         | 
| 38 | 
            +
                  return if overridden?
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  @state = value
         | 
| 41 | 
            +
                  persist
         | 
| 36 42 | 
             
                end
         | 
| 37 43 |  | 
| 38 44 | 
             
                def persist
         | 
| 39 | 
            -
                  persistence_adapters.each{ |adapter| adapter.write state }
         | 
| 45 | 
            +
                  persistence_adapters.each { |adapter| adapter.write state }
         | 
| 40 46 | 
             
                end
         | 
| 41 47 |  | 
| 42 48 | 
             
                private
         | 
| @@ -46,11 +52,7 @@ module FlipFab | |
| 46 52 | 
             
                end
         | 
| 47 53 |  | 
| 48 54 | 
             
                def state
         | 
| 49 | 
            -
                  @state ||=  | 
| 50 | 
            -
                    state_from_context
         | 
| 51 | 
            -
                  else
         | 
| 52 | 
            -
                    default_state
         | 
| 53 | 
            -
                  end
         | 
| 55 | 
            +
                  @state ||= state_in_context? ? state_from_context : default_state
         | 
| 54 56 | 
             
                end
         | 
| 55 57 |  | 
| 56 58 | 
             
                def state_in_context?
         | 
| @@ -62,7 +64,7 @@ module FlipFab | |
| 62 64 | 
             
                end
         | 
| 63 65 |  | 
| 64 66 | 
             
                def first_adapter_with_state
         | 
| 65 | 
            -
                  persistence_adapters.detect{|adapter| !adapter.read.nil?}
         | 
| 67 | 
            +
                  persistence_adapters.detect { |adapter| !adapter.read.nil? }
         | 
| 66 68 | 
             
                end
         | 
| 67 69 |  | 
| 68 70 | 
             
                def default_state
         | 
| @@ -71,8 +73,10 @@ module FlipFab | |
| 71 73 |  | 
| 72 74 | 
             
                def override
         | 
| 73 75 | 
             
                  return unless context.respond_to? :params
         | 
| 76 | 
            +
             | 
| 74 77 | 
             
                  override = context.params[feature.name.to_s]
         | 
| 75 | 
            -
                  return unless %w | 
| 78 | 
            +
                  return unless %w[enabled disabled].include? override
         | 
| 79 | 
            +
             | 
| 76 80 | 
             
                  override.to_sym
         | 
| 77 81 | 
             
                end
         | 
| 78 82 |  | 
| @@ -1,12 +1,11 @@ | |
| 1 1 | 
             
            module FlipFab
         | 
| 2 2 | 
             
              class CookiePersistence < FlipFab::Persistence
         | 
| 3 | 
            +
                COOKIE_PATH = '/'.freeze
         | 
| 4 | 
            +
                COOKIE_DURATION_MONTHS = 12
         | 
| 5 | 
            +
                # See: https://github.com/rails/rails/blob/b1124a2ac88778c0feb0157ac09367cbd204bf01/actionpack/lib/action_dispatch/middleware/cookies.rb#L214
         | 
| 6 | 
            +
                DOMAIN_REGEXP          = /[^.]*\.([^.]*|..\...|...\...)$/
         | 
| 3 7 |  | 
| 4 | 
            -
             | 
| 5 | 
            -
                  COOKIE_DURATION_MONTHS = 12
         | 
| 6 | 
            -
                  # See: https://github.com/rails/rails/blob/b1124a2ac88778c0feb0157ac09367cbd204bf01/actionpack/lib/action_dispatch/middleware/cookies.rb#L214
         | 
| 7 | 
            -
                  DOMAIN_REGEXP          = /[^.]*\.([^.]*|..\...|...\...)$/
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                def initialize feature_name, context
         | 
| 8 | 
            +
                def initialize(feature_name, context)
         | 
| 10 9 | 
             
                  super
         | 
| 11 10 | 
             
                end
         | 
| 12 11 |  | 
| @@ -14,14 +13,10 @@ module FlipFab | |
| 14 13 | 
             
                  value.to_sym unless value.nil?
         | 
| 15 14 | 
             
                end
         | 
| 16 15 |  | 
| 17 | 
            -
                def write | 
| 18 | 
            -
                   | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
                    expires: cookie_expiration,
         | 
| 22 | 
            -
                    domain:  cookie_domain,
         | 
| 23 | 
            -
                    path:    COOKIE_PATH,
         | 
| 24 | 
            -
                  }
         | 
| 16 | 
            +
                def write(state)
         | 
| 17 | 
            +
                  context.response.set_cookie key, value: state,
         | 
| 18 | 
            +
                                                   expires: cookie_expiration,
         | 
| 19 | 
            +
                                                   path: COOKIE_PATH
         | 
| 25 20 | 
             
                end
         | 
| 26 21 |  | 
| 27 22 | 
             
                private
         | 
| @@ -36,9 +31,7 @@ module FlipFab | |
| 36 31 |  | 
| 37 32 | 
             
                # See: https://github.com/rails/rails/blob/b1124a2ac88778c0feb0157ac09367cbd204bf01/actionpack/lib/action_dispatch/middleware/cookies.rb#L286-L294
         | 
| 38 33 | 
             
                def top_level_domain
         | 
| 39 | 
            -
                  if (host !~ /^[\d.]+$/) && (host =~ DOMAIN_REGEXP)
         | 
| 40 | 
            -
                    $&
         | 
| 41 | 
            -
                  end
         | 
| 34 | 
            +
                  Regexp.last_match(0) if (host !~ /^[\d.]+$/) && (host =~ DOMAIN_REGEXP)
         | 
| 42 35 | 
             
                end
         | 
| 43 36 |  | 
| 44 37 | 
             
                def cookie_expiration
         | 
    
        data/lib/flip_fab/feature.rb
    CHANGED
    
    | @@ -1,9 +1,8 @@ | |
| 1 1 | 
             
            module FlipFab
         | 
| 2 2 | 
             
              class Feature
         | 
| 3 | 
            -
             | 
| 4 3 | 
             
                attr_reader :name, :default, :persistence_adapters
         | 
| 5 4 |  | 
| 6 | 
            -
                def initialize | 
| 5 | 
            +
                def initialize(name, options = {})
         | 
| 7 6 | 
             
                  @name                 = name
         | 
| 8 7 | 
             
                  @default              = options[:default] || :disabled
         | 
| 9 8 | 
             
                  @persistence_adapters = options[:persistence_adapters] || [CookiePersistence]
         | 
| @@ -17,7 +16,7 @@ module FlipFab | |
| 17 16 | 
             
                  !enabled?
         | 
| 18 17 | 
             
                end
         | 
| 19 18 |  | 
| 20 | 
            -
                def with_context | 
| 19 | 
            +
                def with_context(context)
         | 
| 21 20 | 
             
                  ContextualFeature.new self, context
         | 
| 22 21 | 
             
                end
         | 
| 23 22 | 
             
              end
         | 
| @@ -4,17 +4,18 @@ module FlipFab | |
| 4 4 | 
             
              class FeaturesByName
         | 
| 5 5 | 
             
                extend Forwardable
         | 
| 6 6 |  | 
| 7 | 
            -
                def initialize  | 
| 7 | 
            +
                def initialize(features_by_name = {})
         | 
| 8 8 | 
             
                  @features_by_name = features_by_name
         | 
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| 11 | 
            -
                def [] | 
| 11 | 
            +
                def [](name)
         | 
| 12 12 | 
             
                  raise "no feature has been defined with the name: #{name}" if @features_by_name[name].nil?
         | 
| 13 | 
            +
             | 
| 13 14 | 
             
                  @features_by_name[name]
         | 
| 14 15 | 
             
                end
         | 
| 15 16 |  | 
| 16 | 
            -
                def with_context | 
| 17 | 
            -
                  FeaturesByName.new Hash[@features_by_name.map{|name, feature| [name, (feature.with_context context)]}]
         | 
| 17 | 
            +
                def with_context(context)
         | 
| 18 | 
            +
                  FeaturesByName.new Hash[@features_by_name.map { |name, feature| [name, (feature.with_context context)] }]
         | 
| 18 19 | 
             
                end
         | 
| 19 20 |  | 
| 20 21 | 
             
                def_delegators :@features_by_name, :[]=, :clear, :count, :each
         | 
    
        data/lib/flip_fab/helper.rb
    CHANGED
    
    
    
        data/lib/flip_fab/persistence.rb
    CHANGED
    
    | @@ -1,9 +1,8 @@ | |
| 1 1 | 
             
            module FlipFab
         | 
| 2 2 | 
             
              class Persistence
         | 
| 3 | 
            -
             | 
| 4 3 | 
             
                attr_reader :feature_name, :context
         | 
| 5 4 |  | 
| 6 | 
            -
                def initialize | 
| 5 | 
            +
                def initialize(feature_name, context)
         | 
| 7 6 | 
             
                  @feature_name = feature_name
         | 
| 8 7 | 
             
                  @context      = context
         | 
| 9 8 | 
             
                end
         | 
| @@ -12,7 +11,7 @@ module FlipFab | |
| 12 11 | 
             
                  raise NotImplementedError
         | 
| 13 12 | 
             
                end
         | 
| 14 13 |  | 
| 15 | 
            -
                def write | 
| 14 | 
            +
                def write(state)
         | 
| 16 15 | 
             
                  raise NotImplementedError
         | 
| 17 16 | 
             
                end
         | 
| 18 17 | 
             
              end
         | 
    
        data/lib/flip_fab/version.rb
    CHANGED
    
    
    
        data/lib/flip_fab.rb
    CHANGED
    
    | @@ -6,19 +6,19 @@ require 'flip_fab/persistence' | |
| 6 6 | 
             
            require 'flip_fab/cookie_persistence'
         | 
| 7 7 |  | 
| 8 8 | 
             
            module FlipFab
         | 
| 9 | 
            -
              extend self
         | 
| 10 | 
            -
             | 
| 11 9 | 
             
              attr_reader :features
         | 
| 12 10 |  | 
| 13 | 
            -
              def define_feature | 
| 11 | 
            +
              def define_feature(name, options = {})
         | 
| 14 12 | 
             
                @features ||= {}
         | 
| 15 13 | 
             
                @features[name] = Feature.new name, options
         | 
| 16 14 | 
             
              end
         | 
| 17 15 |  | 
| 18 16 | 
             
              @features ||= FeaturesByName.new
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              module_function :features, :define_feature
         | 
| 19 19 | 
             
            end
         | 
| 20 20 |  | 
| 21 21 | 
             
            if defined?(ActionController)
         | 
| 22 | 
            -
              ActionController::Base. | 
| 22 | 
            +
              ActionController::Base.include FlipFab::Helper
         | 
| 23 23 | 
             
              ActionController::Base.helper FlipFab::Helper
         | 
| 24 24 | 
             
            end
         | 
    
        data/script/cibuild
    ADDED
    
    
| @@ -1,15 +1,20 @@ | |
| 1 1 | 
             
            module FlipFab
         | 
| 2 | 
            -
              describe ContextualFeature do
         | 
| 3 | 
            -
                let(:override)             { | 
| 2 | 
            +
              describe ContextualFeature do # rubocop:disable Metrics/BlockLength
         | 
| 3 | 
            +
                let(:override)             {}
         | 
| 4 4 | 
             
                let(:default)              { :disabled }
         | 
| 5 5 | 
             
                let(:persistence_adapters) { [TestPersistence] }
         | 
| 6 | 
            -
                let(:feature)              { Feature.new :example_feature,  | 
| 7 | 
            -
                let(:feature_states)       {{ example_feature: :enabled }}
         | 
| 8 | 
            -
                let(:context)              { TestContext.new feature_states,  | 
| 9 | 
            -
                subject{ described_class.new feature, context }
         | 
| 6 | 
            +
                let(:feature)              { Feature.new :example_feature, default: default, persistence_adapters: persistence_adapters }
         | 
| 7 | 
            +
                let(:feature_states)       { { example_feature: :enabled } }
         | 
| 8 | 
            +
                let(:context)              { TestContext.new feature_states, 'example_feature' => override }
         | 
| 9 | 
            +
                subject { described_class.new feature, context }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                describe '.as_json' do
         | 
| 12 | 
            +
                  it 'returns only the feature' do
         | 
| 13 | 
            +
                    expect(subject.as_json.keys).to eq(['feature'])
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 10 16 |  | 
| 11 17 | 
             
                describe '.new' do
         | 
| 12 | 
            -
             | 
| 13 18 | 
             
                  it 'assigns the feature' do
         | 
| 14 19 | 
             
                    expect(subject.feature).to eq(feature)
         | 
| 15 20 | 
             
                  end
         | 
| @@ -22,23 +27,22 @@ module FlipFab | |
| 22 27 | 
             
                    let(:override) { 'disabled' }
         | 
| 23 28 |  | 
| 24 29 | 
             
                    it 'persists the override' do
         | 
| 25 | 
            -
                      expect{ subject }.to change{ feature_states }.from( | 
| 30 | 
            +
                      expect { subject }.to change { feature_states }.from(example_feature: :enabled).to(example_feature: :disabled)
         | 
| 26 31 | 
             
                    end
         | 
| 27 32 |  | 
| 28 33 | 
             
                    context 'when the override provided is not one of enabled or disabled, it does not persist the override' do
         | 
| 29 34 | 
             
                      let(:override) { '' }
         | 
| 30 35 |  | 
| 31 36 | 
             
                      it 'does not persist the override' do
         | 
| 32 | 
            -
                        expect{ subject }.not_to change{ feature_states }.from( | 
| 37 | 
            +
                        expect { subject }.not_to change { feature_states }.from(example_feature: :enabled)
         | 
| 33 38 | 
             
                      end
         | 
| 34 39 | 
             
                    end
         | 
| 35 40 | 
             
                  end
         | 
| 36 41 | 
             
                end
         | 
| 37 42 |  | 
| 38 43 | 
             
                describe '#enabled?' do
         | 
| 39 | 
            -
             | 
| 40 44 | 
             
                  context 'when the feature is enabled in the adapter' do
         | 
| 41 | 
            -
                    let(:feature_states) {{ example_feature: :enabled }}
         | 
| 45 | 
            +
                    let(:feature_states) { { example_feature: :enabled } }
         | 
| 42 46 |  | 
| 43 47 | 
             
                    it 'returns true' do
         | 
| 44 48 | 
             
                      expect(subject.enabled?).to be_truthy
         | 
| @@ -54,7 +58,7 @@ module FlipFab | |
| 54 58 | 
             
                  end
         | 
| 55 59 |  | 
| 56 60 | 
             
                  context 'when the feature is disabled in the adapter' do
         | 
| 57 | 
            -
                    let(:feature_states) {{ example_feature: :disabled }}
         | 
| 61 | 
            +
                    let(:feature_states) { { example_feature: :disabled } }
         | 
| 58 62 |  | 
| 59 63 | 
             
                    it 'returns false' do
         | 
| 60 64 | 
             
                      expect(subject.enabled?).to be_falsey
         | 
| @@ -62,7 +66,7 @@ module FlipFab | |
| 62 66 | 
             
                  end
         | 
| 63 67 |  | 
| 64 68 | 
             
                  context 'when the feature is not specified in the adapter' do
         | 
| 65 | 
            -
                    let(:feature_states) {{ } | 
| 69 | 
            +
                    let(:feature_states) { {} }
         | 
| 66 70 |  | 
| 67 71 | 
             
                    context 'when the default is :enabled' do
         | 
| 68 72 | 
             
                      let(:default) { :enabled }
         | 
| @@ -85,7 +89,7 @@ module FlipFab | |
| 85 89 | 
             
                    let(:persistence_adapters) { [TestPersistence, TestMultiplePersistence] }
         | 
| 86 90 |  | 
| 87 91 | 
             
                    context 'when the first adapter has enabled and the second adapter has nil' do
         | 
| 88 | 
            -
                      let(:feature_states) {{ example_feature: :enabled, different_example_feature: nil }}
         | 
| 92 | 
            +
                      let(:feature_states) { { example_feature: :enabled, different_example_feature: nil } }
         | 
| 89 93 |  | 
| 90 94 | 
             
                      it 'returns true' do
         | 
| 91 95 | 
             
                        expect(subject.enabled?).to be_truthy
         | 
| @@ -93,7 +97,7 @@ module FlipFab | |
| 93 97 | 
             
                    end
         | 
| 94 98 |  | 
| 95 99 | 
             
                    context 'when the first adapter has nil and the second adapter has enabled' do
         | 
| 96 | 
            -
                      let(:feature_states) {{ example_feature: nil, different_example_feature: :enabled }}
         | 
| 100 | 
            +
                      let(:feature_states) { { example_feature: nil, different_example_feature: :enabled } }
         | 
| 97 101 |  | 
| 98 102 | 
             
                      it 'returns true' do
         | 
| 99 103 | 
             
                        expect(subject.enabled?).to be_truthy
         | 
| @@ -101,7 +105,7 @@ module FlipFab | |
| 101 105 | 
             
                    end
         | 
| 102 106 |  | 
| 103 107 | 
             
                    context 'when the first adapter has disabled and the second adapter has enabled' do
         | 
| 104 | 
            -
                      let(:feature_states) {{ example_feature: :disabled, different_example_feature: :enabled }}
         | 
| 108 | 
            +
                      let(:feature_states) { { example_feature: :disabled, different_example_feature: :enabled } }
         | 
| 105 109 |  | 
| 106 110 | 
             
                      it 'returns false' do
         | 
| 107 111 | 
             
                        expect(subject.enabled?).to be_falsey
         | 
| @@ -111,9 +115,8 @@ module FlipFab | |
| 111 115 | 
             
                end
         | 
| 112 116 |  | 
| 113 117 | 
             
                describe '#disabled?' do
         | 
| 114 | 
            -
             | 
| 115 118 | 
             
                  context 'when #enabled? returns true' do
         | 
| 116 | 
            -
                    let(:feature_states) {{ example_feature: :enabled }}
         | 
| 119 | 
            +
                    let(:feature_states) { { example_feature: :enabled } }
         | 
| 117 120 |  | 
| 118 121 | 
             
                    it 'returns false' do
         | 
| 119 122 | 
             
                      expect(subject.disabled?).to be_falsey
         | 
| @@ -121,7 +124,7 @@ module FlipFab | |
| 121 124 | 
             
                  end
         | 
| 122 125 |  | 
| 123 126 | 
             
                  context 'when #enabled? returns false' do
         | 
| 124 | 
            -
                    let(:feature_states) {{ example_feature: :disabled }}
         | 
| 127 | 
            +
                    let(:feature_states) { { example_feature: :disabled } }
         | 
| 125 128 |  | 
| 126 129 | 
             
                    it 'returns true' do
         | 
| 127 130 | 
             
                      expect(subject.disabled?).to be_truthy
         | 
| @@ -130,34 +133,30 @@ module FlipFab | |
| 130 133 | 
             
                end
         | 
| 131 134 |  | 
| 132 135 | 
             
                describe '#state=' do
         | 
| 133 | 
            -
             | 
| 134 136 | 
             
                  context 'when the provided value is not :enabled or :disabled' do
         | 
| 135 | 
            -
             | 
| 136 137 | 
             
                    it 'raises' do
         | 
| 137 | 
            -
                      expect{ subject.state = '' }.to raise_error 'Invalid state provided: ``, possible states are :enabled, :disabled'
         | 
| 138 | 
            -
                      expect{ subject.state = 'enabled' }.to raise_error 'Invalid state provided: `enabled`, possible states are :enabled, :disabled'
         | 
| 138 | 
            +
                      expect { subject.state = '' }.to raise_error 'Invalid state provided: ``, possible states are :enabled, :disabled'
         | 
| 139 | 
            +
                      expect { subject.state = 'enabled' }.to raise_error 'Invalid state provided: `enabled`, possible states are :enabled, :disabled'
         | 
| 139 140 | 
             
                    end
         | 
| 140 141 | 
             
                  end
         | 
| 141 142 |  | 
| 142 143 | 
             
                  context 'when the provided value is :enabled or :disabled' do
         | 
| 143 | 
            -
             | 
| 144 144 | 
             
                    it 'changes the state of the feature' do
         | 
| 145 | 
            -
                      expect{ subject.state = :disabled }.to change{subject.enabled?}.from(true).to(false)
         | 
| 146 | 
            -
                      expect{ subject.state = :enabled }.to change{subject.enabled?}.from(false).to(true)
         | 
| 145 | 
            +
                      expect { subject.state = :disabled }.to change { subject.enabled? }.from(true).to(false)
         | 
| 146 | 
            +
                      expect { subject.state = :enabled }.to change { subject.enabled? }.from(false).to(true)
         | 
| 147 147 | 
             
                    end
         | 
| 148 148 | 
             
                  end
         | 
| 149 149 | 
             
                end
         | 
| 150 150 |  | 
| 151 151 | 
             
                describe '#enable' do
         | 
| 152 | 
            -
             | 
| 153 152 | 
             
                  context 'when the state has been overridden' do
         | 
| 154 153 | 
             
                    let(:override) { 'disabled' }
         | 
| 155 154 |  | 
| 156 155 | 
             
                    context 'and the persistence adapter has the opposite state' do
         | 
| 157 | 
            -
                      let(:feature_states) {{ example_feature: :disabled }}
         | 
| 156 | 
            +
                      let(:feature_states) { { example_feature: :disabled } }
         | 
| 158 157 |  | 
| 159 158 | 
             
                      it 'does not change the state of the feature' do
         | 
| 160 | 
            -
                        expect{subject.enable}.not_to change{subject.enabled?}.from(false)
         | 
| 159 | 
            +
                        expect { subject.enable }.not_to change { subject.enabled? }.from(false)
         | 
| 161 160 | 
             
                      end
         | 
| 162 161 |  | 
| 163 162 | 
             
                      it 'does not persist the state in the adapter' do
         | 
| @@ -169,14 +168,14 @@ module FlipFab | |
| 169 168 |  | 
| 170 169 | 
             
                  context 'when there are multiple persistence adapters' do
         | 
| 171 170 | 
             
                    let(:persistence_adapters) { [TestPersistence, TestMultiplePersistence] }
         | 
| 172 | 
            -
                    let(:feature_states) {{ example_feature: :disabled, different_example_feature: :disabled }}
         | 
| 171 | 
            +
                    let(:feature_states) { { example_feature: :disabled, different_example_feature: :disabled } }
         | 
| 173 172 |  | 
| 174 173 | 
             
                    it 'changes the state of the feature' do
         | 
| 175 | 
            -
                      expect{subject.enable}.to change{subject.enabled?}.from(false).to(true)
         | 
| 174 | 
            +
                      expect { subject.enable }.to change { subject.enabled? }.from(false).to(true)
         | 
| 176 175 | 
             
                    end
         | 
| 177 176 |  | 
| 178 177 | 
             
                    it 'persists the state in the adapters' do
         | 
| 179 | 
            -
                      expect{ subject.enable }.to change{ feature_states }.from( | 
| 178 | 
            +
                      expect { subject.enable }.to change { feature_states }.from(example_feature: :disabled, different_example_feature: :disabled).to(example_feature: :enabled, different_example_feature: :enabled)
         | 
| 180 179 | 
             
                    end
         | 
| 181 180 | 
             
                  end
         | 
| 182 181 |  | 
| @@ -184,37 +183,37 @@ module FlipFab | |
| 184 183 | 
             
                    let(:persistence_adapters) { [TestPersistence] }
         | 
| 185 184 |  | 
| 186 185 | 
             
                    context 'and the persistence adapter has the same state' do
         | 
| 187 | 
            -
                      let(:feature_states) {{ example_feature: :enabled }}
         | 
| 186 | 
            +
                      let(:feature_states) { { example_feature: :enabled } }
         | 
| 188 187 |  | 
| 189 188 | 
             
                      it 'does not change the state of the feature' do
         | 
| 190 | 
            -
                        expect{subject.enable}.not_to change{subject.enabled?}.from(true)
         | 
| 189 | 
            +
                        expect { subject.enable }.not_to change { subject.enabled? }.from(true)
         | 
| 191 190 | 
             
                      end
         | 
| 192 191 | 
             
                    end
         | 
| 193 192 |  | 
| 194 193 | 
             
                    context 'and the persistence adapter has the opposite state' do
         | 
| 195 | 
            -
                      let(:feature_states) {{ example_feature: :disabled }}
         | 
| 194 | 
            +
                      let(:feature_states) { { example_feature: :disabled } }
         | 
| 196 195 |  | 
| 197 196 | 
             
                      it 'changes the state of the feature' do
         | 
| 198 | 
            -
                        expect{subject.enable}.to change{subject.enabled?}.from(false).to(true)
         | 
| 197 | 
            +
                        expect { subject.enable }.to change { subject.enabled? }.from(false).to(true)
         | 
| 199 198 | 
             
                      end
         | 
| 200 199 |  | 
| 201 200 | 
             
                      it 'persists the state in the adapter' do
         | 
| 202 | 
            -
                        expect{ subject.enable }.to change{ feature_states }.from( | 
| 201 | 
            +
                        expect { subject.enable }.to change { feature_states }.from(example_feature: :disabled).to(example_feature: :enabled)
         | 
| 203 202 | 
             
                      end
         | 
| 204 203 | 
             
                    end
         | 
| 205 204 |  | 
| 206 205 | 
             
                    context 'and the persistence adapter has no state' do
         | 
| 207 | 
            -
                      let(:feature_states) {{ } | 
| 206 | 
            +
                      let(:feature_states) { {} }
         | 
| 208 207 |  | 
| 209 208 | 
             
                      context 'and the feature is disabled' do
         | 
| 210 209 | 
             
                        let(:default) { :disabled }
         | 
| 211 210 |  | 
| 212 211 | 
             
                        it 'changes the state of the feature' do
         | 
| 213 | 
            -
                          expect{subject.enable}.to change{subject.enabled?}.from(false).to(true)
         | 
| 212 | 
            +
                          expect { subject.enable }.to change { subject.enabled? }.from(false).to(true)
         | 
| 214 213 | 
             
                        end
         | 
| 215 214 |  | 
| 216 215 | 
             
                        it 'persists the state in the adapter' do
         | 
| 217 | 
            -
                          expect{ subject.enable }.to change{ feature_states }.from({ | 
| 216 | 
            +
                          expect { subject.enable }.to change { feature_states }.from({}).to(example_feature: :enabled)
         | 
| 218 217 | 
             
                        end
         | 
| 219 218 | 
             
                      end
         | 
| 220 219 |  | 
| @@ -222,11 +221,11 @@ module FlipFab | |
| 222 221 | 
             
                        let(:default) { :enabled }
         | 
| 223 222 |  | 
| 224 223 | 
             
                        it 'does not change the state of the feature' do
         | 
| 225 | 
            -
                          expect{subject.enable}.not_to change{subject.enabled?}.from(true)
         | 
| 224 | 
            +
                          expect { subject.enable }.not_to change { subject.enabled? }.from(true)
         | 
| 226 225 | 
             
                        end
         | 
| 227 226 |  | 
| 228 227 | 
             
                        it 'persists the state in the adapter' do
         | 
| 229 | 
            -
                          expect{ subject.enable }.to change{ feature_states }.from({ | 
| 228 | 
            +
                          expect { subject.enable }.to change { feature_states }.from({}).to(example_feature: :enabled)
         | 
| 230 229 | 
             
                        end
         | 
| 231 230 | 
             
                      end
         | 
| 232 231 | 
             
                    end
         | 
| @@ -238,7 +237,7 @@ module FlipFab | |
| 238 237 | 
             
                        let(:default) { :enabled }
         | 
| 239 238 |  | 
| 240 239 | 
             
                        it 'does not change the state of the feature' do
         | 
| 241 | 
            -
                          expect{subject.enable}.not_to change{subject.enabled?}.from(true)
         | 
| 240 | 
            +
                          expect { subject.enable }.not_to change { subject.enabled? }.from(true)
         | 
| 242 241 | 
             
                        end
         | 
| 243 242 | 
             
                      end
         | 
| 244 243 |  | 
| @@ -246,24 +245,22 @@ module FlipFab | |
| 246 245 | 
             
                        let(:default) { :disabled }
         | 
| 247 246 |  | 
| 248 247 | 
             
                        it 'changes the state of the feature' do
         | 
| 249 | 
            -
                          expect{subject.enable}.to change{subject.enabled?}.from(false).to(true)
         | 
| 248 | 
            +
                          expect { subject.enable }.to change { subject.enabled? }.from(false).to(true)
         | 
| 250 249 | 
             
                        end
         | 
| 251 250 | 
             
                      end
         | 
| 252 251 | 
             
                    end
         | 
| 253 252 | 
             
                  end
         | 
| 254 253 | 
             
                end
         | 
| 255 254 |  | 
| 256 | 
            -
             | 
| 257 255 | 
             
                describe '#disable' do
         | 
| 258 | 
            -
             | 
| 259 256 | 
             
                  context 'when the state has been overridden' do
         | 
| 260 257 | 
             
                    let(:override) { 'enabled' }
         | 
| 261 258 |  | 
| 262 259 | 
             
                    context 'and the persistence adapter has the opposite state' do
         | 
| 263 | 
            -
                      let(:feature_states) {{ example_feature: :enabled }}
         | 
| 260 | 
            +
                      let(:feature_states) { { example_feature: :enabled } }
         | 
| 264 261 |  | 
| 265 262 | 
             
                      it 'does not change the state of the feature' do
         | 
| 266 | 
            -
                        expect{subject.disable}.not_to change{subject.disabled?}.from(false)
         | 
| 263 | 
            +
                        expect { subject.disable }.not_to change { subject.disabled? }.from(false)
         | 
| 267 264 | 
             
                      end
         | 
| 268 265 |  | 
| 269 266 | 
             
                      it 'does not persist the state in the adapter' do
         | 
| @@ -277,18 +274,18 @@ module FlipFab | |
| 277 274 | 
             
                    let(:persistence_adapters) { [TestPersistence] }
         | 
| 278 275 |  | 
| 279 276 | 
             
                    context 'and the persistence adapter has the same state' do
         | 
| 280 | 
            -
                      let(:feature_states) {{ example_feature: :disabled }}
         | 
| 277 | 
            +
                      let(:feature_states) { { example_feature: :disabled } }
         | 
| 281 278 |  | 
| 282 279 | 
             
                      it 'does not change the state of the feature' do
         | 
| 283 | 
            -
                        expect{subject.disable}.not_to change{subject.disabled?}.from(true)
         | 
| 280 | 
            +
                        expect { subject.disable }.not_to change { subject.disabled? }.from(true)
         | 
| 284 281 | 
             
                      end
         | 
| 285 282 | 
             
                    end
         | 
| 286 283 |  | 
| 287 284 | 
             
                    context 'and the persistence adapter has the opposite state' do
         | 
| 288 | 
            -
                      let(:feature_states) {{ example_feature: :enabled }}
         | 
| 285 | 
            +
                      let(:feature_states) { { example_feature: :enabled } }
         | 
| 289 286 |  | 
| 290 287 | 
             
                      it 'changes the state of the feature' do
         | 
| 291 | 
            -
                        expect{subject.disable}.to change{subject.disabled?}.from(false).to(true)
         | 
| 288 | 
            +
                        expect { subject.disable }.to change { subject.disabled? }.from(false).to(true)
         | 
| 292 289 | 
             
                      end
         | 
| 293 290 |  | 
| 294 291 | 
             
                      it 'persists the state in the adapter' do
         | 
| @@ -298,13 +295,13 @@ module FlipFab | |
| 298 295 | 
             
                    end
         | 
| 299 296 |  | 
| 300 297 | 
             
                    context 'and the persistence adapter has no state' do
         | 
| 301 | 
            -
                      let(:feature_states) {{ } | 
| 298 | 
            +
                      let(:feature_states) { {} }
         | 
| 302 299 |  | 
| 303 300 | 
             
                      context 'and the feature is enabled' do
         | 
| 304 301 | 
             
                        let(:default) { :enabled }
         | 
| 305 302 |  | 
| 306 303 | 
             
                        it 'changes the state of the feature' do
         | 
| 307 | 
            -
                          expect{subject.disable}.to change{subject.disabled?}.from(false).to(true)
         | 
| 304 | 
            +
                          expect { subject.disable }.to change { subject.disabled? }.from(false).to(true)
         | 
| 308 305 | 
             
                        end
         | 
| 309 306 |  | 
| 310 307 | 
             
                        it 'persists the state in the adapter' do
         | 
| @@ -317,7 +314,7 @@ module FlipFab | |
| 317 314 | 
             
                        let(:default) { :disabled }
         | 
| 318 315 |  | 
| 319 316 | 
             
                        it 'does not change the state of the feature' do
         | 
| 320 | 
            -
                          expect{subject.disable}.not_to change{subject.disabled?}.from(true)
         | 
| 317 | 
            +
                          expect { subject.disable }.not_to change { subject.disabled? }.from(true)
         | 
| 321 318 | 
             
                        end
         | 
| 322 319 |  | 
| 323 320 | 
             
                        it 'persists the state in the adapter' do
         | 
| @@ -334,7 +331,7 @@ module FlipFab | |
| 334 331 | 
             
                        let(:default) { :disabled }
         | 
| 335 332 |  | 
| 336 333 | 
             
                        it 'does not change the state of the feature' do
         | 
| 337 | 
            -
                          expect{subject.disable}.not_to change{subject.disabled?}.from(true)
         | 
| 334 | 
            +
                          expect { subject.disable }.not_to change { subject.disabled? }.from(true)
         | 
| 338 335 | 
             
                        end
         | 
| 339 336 | 
             
                      end
         | 
| 340 337 |  | 
| @@ -342,7 +339,7 @@ module FlipFab | |
| 342 339 | 
             
                        let(:default) { :enabled }
         | 
| 343 340 |  | 
| 344 341 | 
             
                        it 'changes the state of the feature' do
         | 
| 345 | 
            -
                          expect{subject.disable}.to change{subject.disabled?}.from(false).to(true)
         | 
| 342 | 
            +
                          expect { subject.disable }.to change { subject.disabled? }.from(false).to(true)
         | 
| 346 343 | 
             
                        end
         | 
| 347 344 | 
             
                      end
         | 
| 348 345 | 
             
                    end
         | 
| @@ -10,10 +10,10 @@ Feature: Persisting the feature state in a cookie | |
| 10 10 | 
             
                When I persist the feature state in a cookie
         | 
| 11 11 | 
             
                Then the cookie has the path '/'
         | 
| 12 12 |  | 
| 13 | 
            -
              Scenario Outline: The cookie should  | 
| 13 | 
            +
              Scenario Outline: The cookie populated should not contain domain
         | 
| 14 14 | 
             
                Given the host is '<host>'
         | 
| 15 15 | 
             
                 When I persist the feature state in a cookie
         | 
| 16 | 
            -
                 Then the cookie  | 
| 16 | 
            +
                 Then the cookie does not have domain '<cookie domain>'
         | 
| 17 17 |  | 
| 18 18 | 
             
                Examples:
         | 
| 19 19 | 
             
                  | host                           | cookie domain         |
         | 
| @@ -37,7 +37,7 @@ Feature: Persisting the feature state in a cookie | |
| 37 37 |  | 
| 38 38 | 
             
              Scenario: The cookie should expire after 1 year
         | 
| 39 39 | 
             
                When I persist the feature state in a cookie
         | 
| 40 | 
            -
                Then the cookie expires at 'Fri, 22 Jan 2016 15:26:31  | 
| 40 | 
            +
                Then the cookie expires at 'Fri, 22 Jan 2016 15:26:31 GMT'
         | 
| 41 41 |  | 
| 42 42 | 
             
              Scenario Outline: The cookie's value should be the state of the feature
         | 
| 43 43 | 
             
               Given the state of the feature is '<feature state>'
         |