unleash 3.2.1 → 3.2.2
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 +4 -4
 - data/.rubocop.yml +4 -4
 - data/.travis.yml +2 -3
 - data/README.md +2 -2
 - data/lib/unleash.rb +1 -1
 - data/lib/unleash/activation_strategy.rb +16 -3
 - data/lib/unleash/constraint.rb +26 -0
 - data/lib/unleash/context.rb +18 -5
 - data/lib/unleash/feature_toggle.rb +44 -17
 - data/lib/unleash/strategy/flexible_rollout.rb +55 -0
 - data/lib/unleash/strategy/gradual_rollout_sessionid.rb +1 -1
 - data/lib/unleash/strategy/gradual_rollout_userid.rb +2 -2
 - data/lib/unleash/toggle_fetcher.rb +1 -0
 - data/lib/unleash/version.rb +1 -1
 - data/unleash-client.gemspec +2 -2
 - metadata +12 -10
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: da9995687fc28b71318711bd1575d9f5493fb9514cc3f276966323e4a73e15f8
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: e4a29a197e0faeaeaf093bb5a67103ed36aba352facf9633a6845739b17ff58c
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: a7831894183515cbe05fb10520542b93d8a8cd521eab6ff33e9c7a4f88540519b479a76fb360a085b4f1ee059be1d03d0b531eb0daa4ff3db0147380b6f5db52
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 93ce839564b8ef8af502ca148bd3e369f5463d32cd42af1d947be5d751dda88c52d8da71ee5a456658de899afe7fff159655f7f8579cf8030c81ff62655f1804
         
     | 
    
        data/.rubocop.yml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # inherit_from: .rubocop_todo.yml
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            AllCops:
         
     | 
| 
       4 
     | 
    
         
            -
              TargetRubyVersion: 2. 
     | 
| 
      
 4 
     | 
    
         
            +
              TargetRubyVersion: 2.5
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            Naming/PredicateName:
         
     | 
| 
       7 
7 
     | 
    
         
             
              AllowedMethods:
         
     | 
| 
         @@ -9,7 +9,7 @@ Naming/PredicateName: 
     | 
|
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
            Metrics/ClassLength:
         
     | 
| 
       11 
11 
     | 
    
         
             
              Max: 120
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
      
 12 
     | 
    
         
            +
            Layout/LineLength:
         
     | 
| 
       13 
13 
     | 
    
         
             
              Max: 140
         
     | 
| 
       14 
14 
     | 
    
         
             
            Metrics/MethodLength:
         
     | 
| 
       15 
15 
     | 
    
         
             
              Max: 20
         
     | 
| 
         @@ -22,9 +22,9 @@ Metrics/BlockLength: 
     | 
|
| 
       22 
22 
     | 
    
         
             
            Metrics/AbcSize:
         
     | 
| 
       23 
23 
     | 
    
         
             
              Max: 25
         
     | 
| 
       24 
24 
     | 
    
         
             
            Metrics/CyclomaticComplexity:
         
     | 
| 
       25 
     | 
    
         
            -
              Max:  
     | 
| 
      
 25 
     | 
    
         
            +
              Max: 9
         
     | 
| 
       26 
26 
     | 
    
         
             
            Metrics/PerceivedComplexity:
         
     | 
| 
       27 
     | 
    
         
            -
              Max:  
     | 
| 
      
 27 
     | 
    
         
            +
              Max: 9
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
29 
     | 
    
         
             
            Style/Documentation:
         
     | 
| 
       30 
30 
     | 
    
         
             
              Enabled: false
         
     | 
    
        data/.travis.yml
    CHANGED
    
    | 
         @@ -2,14 +2,13 @@ sudo: false 
     | 
|
| 
       2 
2 
     | 
    
         
             
            language: ruby
         
     | 
| 
       3 
3 
     | 
    
         
             
            rvm:
         
     | 
| 
       4 
4 
     | 
    
         
             
            - jruby
         
     | 
| 
      
 5 
     | 
    
         
            +
            - 3.0
         
     | 
| 
       5 
6 
     | 
    
         
             
            - 2.7
         
     | 
| 
       6 
7 
     | 
    
         
             
            - 2.6
         
     | 
| 
       7 
8 
     | 
    
         
             
            - 2.5
         
     | 
| 
       8 
     | 
    
         
            -
            - 2.4
         
     | 
| 
       9 
9 
     | 
    
         
             
            before_install:
         
     | 
| 
       10 
10 
     | 
    
         
             
            - gem install bundler -v 2.1.4
         
     | 
| 
       11 
     | 
    
         
            -
            - git clone --depth 5 --branch v3. 
     | 
| 
       12 
     | 
    
         
            -
              client-specification
         
     | 
| 
      
 11 
     | 
    
         
            +
            - git clone --depth 5 --branch v3.3.0 https://github.com/Unleash/client-specification.git client-specification
         
     | 
| 
       13 
12 
     | 
    
         | 
| 
       14 
13 
     | 
    
         
             
            notifications:
         
     | 
| 
       15 
14 
     | 
    
         
             
              slack:
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -10,10 +10,10 @@ Leverage the [Unleash Server](https://github.com/Unleash/unleash) for powerful f 
     | 
|
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
            ## Supported Ruby Interpreters
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
      
 13 
     | 
    
         
            +
              * MRI 3.0
         
     | 
| 
       13 
14 
     | 
    
         
             
              * MRI 2.7
         
     | 
| 
       14 
15 
     | 
    
         
             
              * MRI 2.6
         
     | 
| 
       15 
16 
     | 
    
         
             
              * MRI 2.5
         
     | 
| 
       16 
     | 
    
         
            -
              * MRI 2.4
         
     | 
| 
       17 
17 
     | 
    
         
             
              * jruby
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
            ## Installation
         
     | 
| 
         @@ -21,7 +21,7 @@ Leverage the [Unleash Server](https://github.com/Unleash/unleash) for powerful f 
     | 
|
| 
       21 
21 
     | 
    
         
             
            Add this line to your application's Gemfile:
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
            ```ruby
         
     | 
| 
       24 
     | 
    
         
            -
            gem 'unleash', '~> 3.2. 
     | 
| 
      
 24 
     | 
    
         
            +
            gem 'unleash', '~> 3.2.2'
         
     | 
| 
       25 
25 
     | 
    
         
             
            ```
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
            And then execute:
         
     | 
    
        data/lib/unleash.rb
    CHANGED
    
    | 
         @@ -5,7 +5,7 @@ require 'unleash/context' 
     | 
|
| 
       5 
5 
     | 
    
         
             
            require 'unleash/client'
         
     | 
| 
       6 
6 
     | 
    
         
             
            require 'logger'
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
            Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path }
         
     | 
| 
      
 8 
     | 
    
         
            +
            Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path unless path.end_with? '_spec.rb' }
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
            module Unleash
         
     | 
| 
       11 
11 
     | 
    
         
             
              TIME_RESOLUTION = 3
         
     | 
| 
         @@ -1,16 +1,29 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Unleash
         
     | 
| 
       2 
2 
     | 
    
         
             
              class ActivationStrategy
         
     | 
| 
       3 
     | 
    
         
            -
                attr_accessor :name, :params
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :name, :params, :constraints
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
                def initialize(name, params)
         
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(name, params, constraints = [])
         
     | 
| 
       6 
6 
     | 
    
         
             
                  self.name = name
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
                  if params.is_a?(Hash)
         
     | 
| 
       9 
9 
     | 
    
         
             
                    self.params = params
         
     | 
| 
      
 10 
     | 
    
         
            +
                  elsif params.nil?
         
     | 
| 
      
 11 
     | 
    
         
            +
                    self.params = {}
         
     | 
| 
       10 
12 
     | 
    
         
             
                  else
         
     | 
| 
       11 
     | 
    
         
            -
                    Unleash.logger.warn "Invalid params provided for ActivationStrategy  
     | 
| 
      
 13 
     | 
    
         
            +
                    Unleash.logger.warn "Invalid params provided for ActivationStrategy (params:#{params})"
         
     | 
| 
       12 
14 
     | 
    
         
             
                    self.params = {}
         
     | 
| 
       13 
15 
     | 
    
         
             
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  if constraints.is_a?(Array) && constraints.each{ |c| c.is_a?(Constraint) }
         
     | 
| 
      
 18 
     | 
    
         
            +
                    self.constraints = constraints
         
     | 
| 
      
 19 
     | 
    
         
            +
                  else
         
     | 
| 
      
 20 
     | 
    
         
            +
                    Unleash.logger.warn "Invalid constraints provided for ActivationStrategy (contraints: #{constraints})"
         
     | 
| 
      
 21 
     | 
    
         
            +
                    self.constraints = []
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def matches_context?(context)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  self.constraints.any?{ |c| c.matches_context? context }
         
     | 
| 
       14 
27 
     | 
    
         
             
                end
         
     | 
| 
       15 
28 
     | 
    
         
             
              end
         
     | 
| 
       16 
29 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Unleash
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Constraint
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :context_name, :operator, :values
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                VALID_OPERATORS = ['IN', 'NOT_IN'].freeze
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(context_name, operator, values = [])
         
     | 
| 
      
 8 
     | 
    
         
            +
                  raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  raise ArgumentError, "operator does not hold a valid value:" + VALID_OPERATORS unless VALID_OPERATORS.include? operator
         
     | 
| 
      
 10 
     | 
    
         
            +
                  raise ArgumentError, "values does not hold an Array" unless values.is_a?(Array)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  self.context_name = context_name
         
     | 
| 
      
 13 
     | 
    
         
            +
                  self.operator = operator
         
     | 
| 
      
 14 
     | 
    
         
            +
                  self.values = values
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def matches_context?(context)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  Unleash.logger.debug "Unleash::Constraint matches_context? values: #{self.values} context.get_by_name(#{self.context_name})" \
         
     | 
| 
      
 19 
     | 
    
         
            +
                    " #{context.get_by_name(self.context_name)} "
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  is_included = self.values.include? context.get_by_name(self.context_name)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  operator == 'IN' ? is_included : !is_included
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/unleash/context.rb
    CHANGED
    
    | 
         @@ -1,6 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Unleash
         
     | 
| 
       2 
2 
     | 
    
         
             
              class Context
         
     | 
| 
       3 
     | 
    
         
            -
                 
     | 
| 
      
 3 
     | 
    
         
            +
                ATTRS = [:app_name, :environment, :user_id, :session_id, :remote_address].freeze
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                attr_accessor(*[ATTRS, :properties].flatten)
         
     | 
| 
       4 
6 
     | 
    
         | 
| 
       5 
7 
     | 
    
         
             
                def initialize(params = {})
         
     | 
| 
       6 
8 
     | 
    
         
             
                  raise ArgumentError, "Unleash::Context must be initialized with a hash." unless params.is_a?(Hash)
         
     | 
| 
         @@ -12,18 +14,29 @@ module Unleash 
     | 
|
| 
       12 
14 
     | 
    
         
             
                  self.remote_address = value_for('remoteAddress', params)
         
     | 
| 
       13 
15 
     | 
    
         | 
| 
       14 
16 
     | 
    
         
             
                  properties = value_for('properties', params)
         
     | 
| 
       15 
     | 
    
         
            -
                  self.properties = properties.is_a?(Hash) ? properties : {}
         
     | 
| 
      
 17 
     | 
    
         
            +
                  self.properties = properties.is_a?(Hash) ? properties.transform_keys(&:to_sym) : {}
         
     | 
| 
       16 
18 
     | 
    
         
             
                end
         
     | 
| 
       17 
19 
     | 
    
         | 
| 
       18 
20 
     | 
    
         
             
                def to_s
         
     | 
| 
       19 
     | 
    
         
            -
                  "<Context: user_id=#{ 
     | 
| 
      
 21 
     | 
    
         
            +
                  "<Context: user_id=#{@user_id},session_id=#{@session_id},remote_address=#{@remote_address},properties=#{@properties}" \
         
     | 
| 
      
 22 
     | 
    
         
            +
                    ",app_name=#{@app_name},environment=#{@environment}>"
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def get_by_name(name)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  normalized_name = underscore(name).to_sym
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  if ATTRS.include? normalized_name
         
     | 
| 
      
 29 
     | 
    
         
            +
                    self.send(normalized_name)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  else
         
     | 
| 
      
 31 
     | 
    
         
            +
                    self.properties.fetch(normalized_name)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
       20 
33 
     | 
    
         
             
                end
         
     | 
| 
       21 
34 
     | 
    
         | 
| 
       22 
35 
     | 
    
         
             
                private
         
     | 
| 
       23 
36 
     | 
    
         | 
| 
       24 
37 
     | 
    
         
             
                # Method to fetch values from hash for two types of keys: string in camelCase and symbol in snake_case
         
     | 
| 
       25 
     | 
    
         
            -
                def value_for(key, params, default_value =  
     | 
| 
       26 
     | 
    
         
            -
                  params.values_at(key, underscore(key).to_sym).compact.first || default_value
         
     | 
| 
      
 38 
     | 
    
         
            +
                def value_for(key, params, default_value = nil)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  params.values_at(key, key.to_sym, underscore(key), underscore(key).to_sym).compact.first || default_value
         
     | 
| 
       27 
40 
     | 
    
         
             
                end
         
     | 
| 
       28 
41 
     | 
    
         | 
| 
       29 
42 
     | 
    
         
             
                # converts CamelCase to snake_case
         
     | 
| 
         @@ -1,4 +1,5 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'unleash/activation_strategy'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'unleash/constraint'
         
     | 
| 
       2 
3 
     | 
    
         
             
            require 'unleash/variant_definition'
         
     | 
| 
       3 
4 
     | 
    
         
             
            require 'unleash/variant'
         
     | 
| 
       4 
5 
     | 
    
         
             
            require 'unleash/strategy/util'
         
     | 
| 
         @@ -13,20 +14,9 @@ module Unleash 
     | 
|
| 
       13 
14 
     | 
    
         | 
| 
       14 
15 
     | 
    
         
             
                  self.name       = params.fetch('name', nil)
         
     | 
| 
       15 
16 
     | 
    
         
             
                  self.enabled    = params.fetch('enabled', false)
         
     | 
| 
       16 
     | 
    
         
            -
                  self.strategies = params.fetch('strategies', [])
         
     | 
| 
       17 
     | 
    
         
            -
                    .select{ |s| s.has_key?('name') && Unleash::STRATEGIES.has_key?(s['name'].to_sym) }
         
     | 
| 
       18 
     | 
    
         
            -
                    .map{ |s| ActivationStrategy.new(s['name'], s['parameters'] || {}) } || []
         
     | 
| 
       19 
17 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
                  self. 
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
                    .map do |v|
         
     | 
| 
       23 
     | 
    
         
            -
                      VariantDefinition.new(
         
     | 
| 
       24 
     | 
    
         
            -
                        v.fetch('name', ''),
         
     | 
| 
       25 
     | 
    
         
            -
                        v.fetch('weight', 0),
         
     | 
| 
       26 
     | 
    
         
            -
                        v.fetch('payload', nil),
         
     | 
| 
       27 
     | 
    
         
            -
                        v.fetch('overrides', [])
         
     | 
| 
       28 
     | 
    
         
            -
                      )
         
     | 
| 
       29 
     | 
    
         
            -
                    end || []
         
     | 
| 
      
 18 
     | 
    
         
            +
                  self.strategies = initialize_strategies(params)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  self.variant_definitions = initialize_variant_definitions(params)
         
     | 
| 
       30 
20 
     | 
    
         
             
                end
         
     | 
| 
       31 
21 
     | 
    
         | 
| 
       32 
22 
     | 
    
         
             
                def to_s
         
     | 
| 
         @@ -64,23 +54,29 @@ module Unleash 
     | 
|
| 
       64 
54 
     | 
    
         
             
                  result =
         
     | 
| 
       65 
55 
     | 
    
         
             
                    if self.enabled
         
     | 
| 
       66 
56 
     | 
    
         
             
                      self.strategies.empty? ||
         
     | 
| 
       67 
     | 
    
         
            -
                        self.strategies.any? 
     | 
| 
      
 57 
     | 
    
         
            +
                        self.strategies.any? do |s|
         
     | 
| 
      
 58 
     | 
    
         
            +
                          strategy_enabled?(s, context) && strategy_constraint_matches?(s, context)
         
     | 
| 
      
 59 
     | 
    
         
            +
                        end
         
     | 
| 
       68 
60 
     | 
    
         
             
                    else
         
     | 
| 
       69 
61 
     | 
    
         
             
                      default_result
         
     | 
| 
       70 
62 
     | 
    
         
             
                    end
         
     | 
| 
       71 
63 
     | 
    
         | 
| 
       72 
     | 
    
         
            -
                  Unleash.logger.debug "FeatureToggle (enabled:#{self.enabled} default_result:#{default_result} " \
         
     | 
| 
       73 
     | 
    
         
            -
                    "and Strategies combined returned #{result})"
         
     | 
| 
      
 64 
     | 
    
         
            +
                  Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled} default_result:#{default_result} " \
         
     | 
| 
      
 65 
     | 
    
         
            +
                    "and Strategies combined with contraints returned #{result})"
         
     | 
| 
       74 
66 
     | 
    
         | 
| 
       75 
67 
     | 
    
         
             
                  result
         
     | 
| 
       76 
68 
     | 
    
         
             
                end
         
     | 
| 
       77 
69 
     | 
    
         | 
| 
       78 
70 
     | 
    
         
             
                def strategy_enabled?(strategy, context)
         
     | 
| 
       79 
71 
     | 
    
         
             
                  r = Unleash::STRATEGIES.fetch(strategy.name.to_sym, :unknown).is_enabled?(strategy.params, context)
         
     | 
| 
       80 
     | 
    
         
            -
                  Unleash.logger.debug "Strategy #{strategy.name} returned #{r} with context: #{context}" 
     | 
| 
      
 72 
     | 
    
         
            +
                  Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
         
     | 
| 
       81 
73 
     | 
    
         
             
                  r
         
     | 
| 
       82 
74 
     | 
    
         
             
                end
         
     | 
| 
       83 
75 
     | 
    
         | 
| 
      
 76 
     | 
    
         
            +
                def strategy_constraint_matches?(strategy, context)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
       84 
80 
     | 
    
         
             
                def disabled_variant
         
     | 
| 
       85 
81 
     | 
    
         
             
                  Unleash::Variant.new(name: 'disabled', enabled: false)
         
     | 
| 
       86 
82 
     | 
    
         
             
                end
         
     | 
| 
         @@ -127,5 +123,36 @@ module Unleash 
     | 
|
| 
       127 
123 
     | 
    
         
             
                  end
         
     | 
| 
       128 
124 
     | 
    
         
             
                  context
         
     | 
| 
       129 
125 
     | 
    
         
             
                end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                def initialize_strategies(params)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  params.fetch('strategies', [])
         
     | 
| 
      
 129 
     | 
    
         
            +
                    .select{ |s| s.has_key?('name') && Unleash::STRATEGIES.has_key?(s['name'].to_sym) }
         
     | 
| 
      
 130 
     | 
    
         
            +
                    .map do |s|
         
     | 
| 
      
 131 
     | 
    
         
            +
                      ActivationStrategy.new(
         
     | 
| 
      
 132 
     | 
    
         
            +
                        s['name'],
         
     | 
| 
      
 133 
     | 
    
         
            +
                        s['parameters'],
         
     | 
| 
      
 134 
     | 
    
         
            +
                        (s['constraints'] || []).map do |c|
         
     | 
| 
      
 135 
     | 
    
         
            +
                          Constraint.new(
         
     | 
| 
      
 136 
     | 
    
         
            +
                            c.fetch('contextName'),
         
     | 
| 
      
 137 
     | 
    
         
            +
                            c.fetch('operator'),
         
     | 
| 
      
 138 
     | 
    
         
            +
                            c.fetch('values')
         
     | 
| 
      
 139 
     | 
    
         
            +
                          )
         
     | 
| 
      
 140 
     | 
    
         
            +
                        end
         
     | 
| 
      
 141 
     | 
    
         
            +
                      )
         
     | 
| 
      
 142 
     | 
    
         
            +
                    end || []
         
     | 
| 
      
 143 
     | 
    
         
            +
                end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                def initialize_variant_definitions(params)
         
     | 
| 
      
 146 
     | 
    
         
            +
                  (params.fetch('variants', []) || [])
         
     | 
| 
      
 147 
     | 
    
         
            +
                    .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
         
     | 
| 
      
 148 
     | 
    
         
            +
                    .map do |v|
         
     | 
| 
      
 149 
     | 
    
         
            +
                      VariantDefinition.new(
         
     | 
| 
      
 150 
     | 
    
         
            +
                        v.fetch('name', ''),
         
     | 
| 
      
 151 
     | 
    
         
            +
                        v.fetch('weight', 0),
         
     | 
| 
      
 152 
     | 
    
         
            +
                        v.fetch('payload', nil),
         
     | 
| 
      
 153 
     | 
    
         
            +
                        v.fetch('overrides', [])
         
     | 
| 
      
 154 
     | 
    
         
            +
                      )
         
     | 
| 
      
 155 
     | 
    
         
            +
                    end || []
         
     | 
| 
      
 156 
     | 
    
         
            +
                end
         
     | 
| 
       130 
157 
     | 
    
         
             
              end
         
     | 
| 
       131 
158 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,55 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'unleash/strategy/util'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Unleash
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Strategy
         
     | 
| 
      
 5 
     | 
    
         
            +
                class FlexibleRollout < Base
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def name
         
     | 
| 
      
 7 
     | 
    
         
            +
                    'flexibleRollout'
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  # need: params['percentage']
         
     | 
| 
      
 11 
     | 
    
         
            +
                  def is_enabled?(params = {}, context = nil)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    return false unless params.is_a?(Hash)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    return false unless context.class.name == 'Unleash::Context'
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    stickiness = params.fetch('stickiness', 'default')
         
     | 
| 
      
 16 
     | 
    
         
            +
                    stickiness_id = resolve_stickiness(stickiness, context)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 19 
     | 
    
         
            +
                      percentage = Integer(params.fetch('rollout', 0))
         
     | 
| 
      
 20 
     | 
    
         
            +
                      percentage = 0 if percentage > 100 || percentage.negative?
         
     | 
| 
      
 21 
     | 
    
         
            +
                    rescue ArgumentError
         
     | 
| 
      
 22 
     | 
    
         
            +
                      return false
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                    group_id = params.fetch('groupId', '')
         
     | 
| 
      
 26 
     | 
    
         
            +
                    normalized_number = Util.get_normalized_number(stickiness_id, group_id)
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    return false if stickiness_id.nil?
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    (percentage.positive? && normalized_number <= percentage)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  private
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def random
         
     | 
| 
      
 36 
     | 
    
         
            +
                    Random.rand(0..100)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def resolve_stickiness(stickiness, context)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    case stickiness
         
     | 
| 
      
 41 
     | 
    
         
            +
                    when 'userId'
         
     | 
| 
      
 42 
     | 
    
         
            +
                      context.user_id
         
     | 
| 
      
 43 
     | 
    
         
            +
                    when 'sessionId'
         
     | 
| 
      
 44 
     | 
    
         
            +
                      context.session_id
         
     | 
| 
      
 45 
     | 
    
         
            +
                    when 'random'
         
     | 
| 
      
 46 
     | 
    
         
            +
                      random
         
     | 
| 
      
 47 
     | 
    
         
            +
                    when 'default'
         
     | 
| 
      
 48 
     | 
    
         
            +
                      context.user_id || context.session_id || random
         
     | 
| 
      
 49 
     | 
    
         
            +
                    else
         
     | 
| 
      
 50 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -11,7 +11,7 @@ module Unleash 
     | 
|
| 
       11 
11 
     | 
    
         
             
                  def is_enabled?(params = {}, context = nil)
         
     | 
| 
       12 
12 
     | 
    
         
             
                    return false unless params.is_a?(Hash) && params.has_key?('percentage')
         
     | 
| 
       13 
13 
     | 
    
         
             
                    return false unless context.class.name == 'Unleash::Context'
         
     | 
| 
       14 
     | 
    
         
            -
                    return false if context.session_id.empty?
         
     | 
| 
      
 14 
     | 
    
         
            +
                    return false if context.session_id.nil? || context.session_id.empty?
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
                    percentage = Integer(params['percentage'] || 0)
         
     | 
| 
       17 
17 
     | 
    
         
             
                    (percentage.positive? && Util.get_normalized_number(context.session_id, params['groupId'] || "") <= percentage)
         
     | 
| 
         @@ -8,10 +8,10 @@ module Unleash 
     | 
|
| 
       8 
8 
     | 
    
         
             
                  end
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
                  # need: params['percentage'], params['groupId'], context.user_id,
         
     | 
| 
       11 
     | 
    
         
            -
                  def is_enabled?(params = {}, context = nil)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  def is_enabled?(params = {}, context = nil, _constraints = [])
         
     | 
| 
       12 
12 
     | 
    
         
             
                    return false unless params.is_a?(Hash) && params.has_key?('percentage')
         
     | 
| 
       13 
13 
     | 
    
         
             
                    return false unless context.class.name == 'Unleash::Context'
         
     | 
| 
       14 
     | 
    
         
            -
                    return false if context.user_id.empty?
         
     | 
| 
      
 14 
     | 
    
         
            +
                    return false if context.user_id.nil? || context.user_id.empty?
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
                    percentage = Integer(params['percentage'] || 0)
         
     | 
| 
       17 
17 
     | 
    
         
             
                    (percentage.positive? && Util.get_normalized_number(context.user_id, params['groupId'] || "") <= percentage)
         
     | 
    
        data/lib/unleash/version.rb
    CHANGED
    
    
    
        data/unleash-client.gemspec
    CHANGED
    
    | 
         @@ -21,14 +21,14 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       21 
21 
     | 
    
         
             
              spec.bindir        = 'bin'
         
     | 
| 
       22 
22 
     | 
    
         
             
              spec.executables   = spec.files.grep(%r{^bin/unleash}) { |f| File.basename(f) }
         
     | 
| 
       23 
23 
     | 
    
         
             
              spec.require_paths = ["lib"]
         
     | 
| 
       24 
     | 
    
         
            -
              spec.required_ruby_version = " 
     | 
| 
      
 24 
     | 
    
         
            +
              spec.required_ruby_version = ">= 2.5"
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
26 
     | 
    
         
             
              spec.add_dependency "murmurhash3", "~> 0.1.6"
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
28 
     | 
    
         
             
              spec.add_development_dependency "bundler", "~> 2.1"
         
     | 
| 
       29 
29 
     | 
    
         
             
              spec.add_development_dependency "rake", "~> 12.3"
         
     | 
| 
       30 
30 
     | 
    
         
             
              spec.add_development_dependency "rspec", "~> 3.9"
         
     | 
| 
       31 
     | 
    
         
            -
              spec.add_development_dependency "rspec-json_expectations", "~> 2. 
     | 
| 
      
 31 
     | 
    
         
            +
              spec.add_development_dependency "rspec-json_expectations", "~> 2.2"
         
     | 
| 
       32 
32 
     | 
    
         
             
              spec.add_development_dependency "webmock", "~> 3.8"
         
     | 
| 
       33 
33 
     | 
    
         | 
| 
       34 
34 
     | 
    
         
             
              spec.add_development_dependency "coveralls", "~> 0.8"
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: unleash
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 3.2. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 3.2.2
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Renato Arruda
         
     | 
| 
       8 
     | 
    
         
            -
            autorequire: 
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2021-03-26 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: murmurhash3
         
     | 
| 
         @@ -72,14 +72,14 @@ dependencies: 
     | 
|
| 
       72 
72 
     | 
    
         
             
                requirements:
         
     | 
| 
       73 
73 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       74 
74 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       75 
     | 
    
         
            -
                    version: '2. 
     | 
| 
      
 75 
     | 
    
         
            +
                    version: '2.2'
         
     | 
| 
       76 
76 
     | 
    
         
             
              type: :development
         
     | 
| 
       77 
77 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       78 
78 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       79 
79 
     | 
    
         
             
                requirements:
         
     | 
| 
       80 
80 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       81 
81 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       82 
     | 
    
         
            -
                    version: '2. 
     | 
| 
      
 82 
     | 
    
         
            +
                    version: '2.2'
         
     | 
| 
       83 
83 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       84 
84 
     | 
    
         
             
              name: webmock
         
     | 
| 
       85 
85 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -148,6 +148,7 @@ files: 
     | 
|
| 
       148 
148 
     | 
    
         
             
            - lib/unleash/activation_strategy.rb
         
     | 
| 
       149 
149 
     | 
    
         
             
            - lib/unleash/client.rb
         
     | 
| 
       150 
150 
     | 
    
         
             
            - lib/unleash/configuration.rb
         
     | 
| 
      
 151 
     | 
    
         
            +
            - lib/unleash/constraint.rb
         
     | 
| 
       151 
152 
     | 
    
         
             
            - lib/unleash/context.rb
         
     | 
| 
       152 
153 
     | 
    
         
             
            - lib/unleash/feature_toggle.rb
         
     | 
| 
       153 
154 
     | 
    
         
             
            - lib/unleash/metrics.rb
         
     | 
| 
         @@ -156,6 +157,7 @@ files: 
     | 
|
| 
       156 
157 
     | 
    
         
             
            - lib/unleash/strategy/application_hostname.rb
         
     | 
| 
       157 
158 
     | 
    
         
             
            - lib/unleash/strategy/base.rb
         
     | 
| 
       158 
159 
     | 
    
         
             
            - lib/unleash/strategy/default.rb
         
     | 
| 
      
 160 
     | 
    
         
            +
            - lib/unleash/strategy/flexible_rollout.rb
         
     | 
| 
       159 
161 
     | 
    
         
             
            - lib/unleash/strategy/gradual_rollout_random.rb
         
     | 
| 
       160 
162 
     | 
    
         
             
            - lib/unleash/strategy/gradual_rollout_sessionid.rb
         
     | 
| 
       161 
163 
     | 
    
         
             
            - lib/unleash/strategy/gradual_rollout_userid.rb
         
     | 
| 
         @@ -173,23 +175,23 @@ homepage: https://github.com/unleash/unleash-client-ruby 
     | 
|
| 
       173 
175 
     | 
    
         
             
            licenses:
         
     | 
| 
       174 
176 
     | 
    
         
             
            - Apache-2.0
         
     | 
| 
       175 
177 
     | 
    
         
             
            metadata: {}
         
     | 
| 
       176 
     | 
    
         
            -
            post_install_message: 
     | 
| 
      
 178 
     | 
    
         
            +
            post_install_message:
         
     | 
| 
       177 
179 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       178 
180 
     | 
    
         
             
            require_paths:
         
     | 
| 
       179 
181 
     | 
    
         
             
            - lib
         
     | 
| 
       180 
182 
     | 
    
         
             
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
       181 
183 
     | 
    
         
             
              requirements:
         
     | 
| 
       182 
     | 
    
         
            -
              - - " 
     | 
| 
      
 184 
     | 
    
         
            +
              - - ">="
         
     | 
| 
       183 
185 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       184 
     | 
    
         
            -
                  version: '2. 
     | 
| 
      
 186 
     | 
    
         
            +
                  version: '2.5'
         
     | 
| 
       185 
187 
     | 
    
         
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
       186 
188 
     | 
    
         
             
              requirements:
         
     | 
| 
       187 
189 
     | 
    
         
             
              - - ">="
         
     | 
| 
       188 
190 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       189 
191 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       190 
192 
     | 
    
         
             
            requirements: []
         
     | 
| 
       191 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
       192 
     | 
    
         
            -
            signing_key: 
     | 
| 
      
 193 
     | 
    
         
            +
            rubygems_version: 3.2.3
         
     | 
| 
      
 194 
     | 
    
         
            +
            signing_key:
         
     | 
| 
       193 
195 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       194 
196 
     | 
    
         
             
            summary: Unleash feature toggle client.
         
     | 
| 
       195 
197 
     | 
    
         
             
            test_files: []
         
     |