growthbook 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,80 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Growthbook
4
+ # Class for creating an inline experiment for evaluating
4
5
  class InlineExperiment
5
- # @returns [String]
6
+ # @return [String] The globally unique identifier for the experiment
6
7
  attr_accessor :key
7
8
 
8
- # @returns [Any]
9
+ # @return [Array<String, Integer, Hash>] The different variations to choose between
9
10
  attr_accessor :variations
10
11
 
11
- # @returns [Bool]
12
- attr_accessor :active
13
-
14
- # @returns [Integer, nil]
15
- attr_accessor :force
16
-
17
- # @returns [Array<Float>, nil]
12
+ # @return [Array<Float>] How to weight traffic between variations. Must add to 1.
18
13
  attr_accessor :weights
19
14
 
20
- # @returns [Float]
15
+ # @return [true, false] If set to false, always return the control (first variation)
16
+ attr_accessor :active
17
+
18
+ # @return [Float] What percent of users should be included in the experiment (between 0 and 1, inclusive)
21
19
  attr_accessor :coverage
22
20
 
23
- # @returns [Hash, nil]
21
+ # @return [Array<Hash>] Array of ranges, one per variation
22
+ attr_accessor :ranges
23
+
24
+ # @return [Hash] Optional targeting condition
24
25
  attr_accessor :condition
25
26
 
26
- # @returns [Array]
27
+ # @return [String, nil] Adds the experiment to a namespace
27
28
  attr_accessor :namespace
28
29
 
29
- # @returns [String]
30
+ # @return [integer, nil] All users included in the experiment will be forced into the specific variation index
31
+ attr_accessor :force
32
+
33
+ # @return [String] What user attribute should be used to assign variations (defaults to id)
30
34
  attr_accessor :hash_attribute
31
35
 
32
- # Constructor for an Experiment
33
- #
34
- # @param options [Hash]
35
- # @option options [Array<Any>] :variations The variations to pick between
36
- # @option options [String] :key The unique identifier for this experiment
37
- # @option options [Float] :coverage (1.0) The percent of elegible traffic to include in the experiment
38
- # @option options [Array<Float>] :weights The relative weights of the variations.
39
- # Length must be the same as the number of variations. Total should add to 1.0.
40
- # Default is an even split between variations
41
- # @option options [Boolean] :anon (false) If false, the experiment uses the logged-in user id for bucketing
42
- # If true, the experiment uses the anonymous id for bucketing
43
- # @option options [Array<String>] :targeting Array of targeting rules in the format "key op value"
44
- # where op is one of: =, !=, <, >, ~, !~
45
- # @option options [Integer, nil] :force If an integer, force all users to get this variation
46
- # @option options [Hash] :data Data to attach to the variations
47
- def initialize(options = {})
48
- @key = getOption(options, :key, '').to_s
49
- @variations = getOption(options, :variations, [])
50
- @active = getOption(options, :active, true)
51
- @force = getOption(options, :force)
52
- @weights = getOption(options, :weights)
53
- @coverage = getOption(options, :coverage, 1)
54
- @condition = getOption(options, :condition)
55
- @namespace = getOption(options, :namespace)
56
- @hash_attribute = getOption(options, :hash_attribute) || getOption(options, :hashAttribute) || 'id'
57
- end
36
+ # @return [Integer] The hash version to use (default to 1)
37
+ attr_accessor :hash_version
58
38
 
59
- def getOption(hash, key, default = nil)
60
- return hash[key.to_sym] if hash.key?(key.to_sym)
61
- return hash[key.to_s] if hash.key?(key.to_s)
39
+ # @return [Array<Hash>] Meta info about the variations
40
+ attr_accessor :meta
62
41
 
63
- default
42
+ # @return [Array<Hash>] Array of filters to apply
43
+ attr_accessor :filters
44
+
45
+ # @return [String, nil] The hash seed to use
46
+ attr_accessor :seed
47
+
48
+ # @return [String] Human-readable name for the experiment
49
+ attr_accessor :name
50
+
51
+ # @return [String, nil] Id of the current experiment phase
52
+ attr_accessor :phase
53
+
54
+ def initialize(options = {})
55
+ @key = get_option(options, :key, '').to_s
56
+ @variations = get_option(options, :variations, [])
57
+ @weights = get_option(options, :weights)
58
+ @active = get_option(options, :active, true)
59
+ @coverage = get_option(options, :coverage, 1)
60
+ @ranges = get_option(options, :ranges)
61
+ @condition = get_option(options, :condition)
62
+ @namespace = get_option(options, :namespace)
63
+ @force = get_option(options, :force)
64
+ @hash_attribute = get_option(options, :hash_attribute) || get_option(options, :hashAttribute) || 'id'
65
+ @hash_version = get_option(options, :hash_version) || get_option(options, :hashVersion)
66
+ @meta = get_option(options, :meta)
67
+ @filters = get_option(options, :filters)
68
+ @seed = get_option(options, :seed)
69
+ @name = get_option(options, :name)
70
+ @phase = get_option(options, :phase)
64
71
  end
65
72
 
66
73
  def to_json(*_args)
67
74
  res = {}
68
75
  res['key'] = @key
69
76
  res['variations'] = @variations
70
- res['active'] = @active if @active != true && !@active.nil?
71
- res['force'] = @force unless @force.nil?
72
77
  res['weights'] = @weights unless @weights.nil?
78
+ res['active'] = @active if @active != true && !@active.nil?
73
79
  res['coverage'] = @coverage if @coverage != 1 && !@coverage.nil?
74
- res['condition'] = @condition unless @condition.nil?
75
- res['namespace'] = @namespace unless @namespace.nil?
80
+ res['ranges'] = @ranges
81
+ res['condition'] = @condition
82
+ res['namespace'] = @namespace
83
+ res['force'] = @force unless @force.nil?
76
84
  res['hashAttribute'] = @hash_attribute if @hash_attribute != 'id' && !@hash_attribute.nil?
77
- res
85
+ res['hashVersion'] = @hash_version
86
+ res['meta'] = @meta
87
+ res['filters'] = @filters
88
+ res['seed'] = @seed
89
+ res['name'] = @name
90
+ res['phase'] = @phase
91
+ res.compact
92
+ end
93
+
94
+ private
95
+
96
+ def get_option(hash, key, default = nil)
97
+ return hash[key.to_sym] if hash.key?(key.to_sym)
98
+ return hash[key.to_s] if hash.key?(key.to_s)
99
+
100
+ default
78
101
  end
79
102
  end
80
103
  end
@@ -1,62 +1,81 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Growthbook
4
+ # Result of running an experiment.
4
5
  class InlineExperimentResult
5
- # Whether or not the user is in the experiment
6
- # @return [Bool]
6
+ # @return [Boolean] Whether or not the user is part of the experiment
7
7
  attr_reader :in_experiment
8
8
 
9
- # The array index of the assigned variation
10
- # @return [Integer]
9
+ # @return [Integer] The array index of the assigned variation
11
10
  attr_reader :variation_id
12
11
 
13
- # The assigned variation value
14
- # @return [Any]
12
+ # @return [Any] The array value of the assigned variation
15
13
  attr_reader :value
16
14
 
17
- # If the variation was randomly assigned based on user attribute hashes
18
- # @return [Bool]
15
+ # @return [Bool] If a hash was used to assign a variation
19
16
  attr_reader :hash_used
20
17
 
21
- # The attribute used to split traffic
22
- # @return [String]
18
+ # @return [String] The user attribute used to assign a variation
23
19
  attr_reader :hash_attribute
24
20
 
25
- # The value of the hashAttribute
26
- # @return [String]
21
+ # @return [String] The value of that attribute
27
22
  attr_reader :hash_value
28
23
 
24
+ # @return [String, nil] The id of the feature (if any) that the experiment came from
29
25
  attr_reader :feature_id
30
26
 
31
- def initialize(
32
- hash_used,
33
- in_experiment,
34
- variation_id,
35
- value,
36
- hash_attribute,
37
- hash_value,
38
- feature_id
39
- )
40
-
41
- @hash_used = hash_used
42
- @in_experiment = in_experiment
43
- @variation_id = variation_id
44
- @value = value
45
- @hash_attribute = hash_attribute
46
- @hash_value = hash_value
47
- @feature_id = feature_id
27
+ # @return [String] The unique key for the assigned variation
28
+ attr_reader :key
29
+
30
+ # @return [Float] The hash value used to assign a variation (float from 0 to 1)
31
+ attr_reader :bucket
32
+
33
+ # @return [String , nil] Human-readable name for the experiment
34
+ attr_reader :name
35
+
36
+ # @return [Boolean] Used for holdout groups
37
+ attr_accessor :passthrough
38
+
39
+ def initialize(options = {})
40
+ @key = options[:key]
41
+ @in_experiment = options[:in_experiment]
42
+ @variation_id = options[:variation_id]
43
+ @value = options[:value]
44
+ @hash_used = options[:hash_used]
45
+ @hash_attribute = options[:hash_attribute]
46
+ @hash_value = options[:hash_value]
47
+ @feature_id = options[:feature_id]
48
+ @bucket = options[:bucket]
49
+ @name = options[:name]
50
+ @passthrough = options[:passthrough]
51
+ end
52
+
53
+ # If the variation was randomly assigned based on user attribute hashes
54
+ # @return [Bool]
55
+ def hash_used?
56
+ @hash_used
57
+ end
58
+
59
+ # Whether or not the user is in the experiment
60
+ # @return [Bool]
61
+ def in_experiment?
62
+ @in_experiment
48
63
  end
49
64
 
50
65
  def to_json(*_args)
51
- res = {}
52
- res['inExperiment'] = @in_experiment
53
- res['hashUsed'] = @hash_used
54
- res['variationId'] = @variation_id
55
- res['value'] = @value
56
- res['hashAttribute'] = @hash_attribute
57
- res['hashValue'] = @hash_value
58
- res['featureId'] = @feature_id.to_s
59
- res
66
+ {
67
+ 'inExperiment' => @in_experiment,
68
+ 'variationId' => @variation_id,
69
+ 'value' => @value,
70
+ 'hashUsed' => @hash_used,
71
+ 'hashAttribute' => @hash_attribute,
72
+ 'hashValue' => @hash_value,
73
+ 'featureId' => @feature_id.to_s,
74
+ 'key' => @key.to_s,
75
+ 'bucket' => @bucket,
76
+ 'name' => @name,
77
+ 'passthrough' => @passthrough
78
+ }.compact
60
79
  end
61
80
  end
62
81
  end
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fnv'
4
+ require 'bigdecimal'
5
+ require 'bigdecimal/util'
4
6
 
5
7
  module Growthbook
8
+ # internal use only
6
9
  class Util
7
- def self.checkRule(actual, op, desired)
10
+ def self.check_rule(actual, op, desired)
8
11
  # Check if both strings are numeric so we can do natural ordering
9
12
  # for greater than / less than operators
10
13
  numeric = begin
@@ -15,9 +18,9 @@ module Growthbook
15
18
 
16
19
  case op
17
20
  when '='
18
- numeric ? Float(actual) == Float(desired) : actual == desired
21
+ numeric ? Float(actual).to_d == Float(desired).to_d : actual == desired
19
22
  when '!='
20
- numeric ? Float(actual) != Float(desired) : actual != desired
23
+ numeric ? Float(actual).to_d != Float(desired).to_d : actual != desired
21
24
  when '>'
22
25
  numeric ? Float(actual) > Float(desired) : actual > desired
23
26
  when '<'
@@ -39,61 +42,41 @@ module Growthbook
39
42
  end
40
43
  end
41
44
 
42
- def self.chooseVariation(userId, experiment)
43
- testId = experiment.id
44
- weights = experiment.getScaledWeights
45
+ def self.hash(seed:, value:, version:)
46
+ return (FNV.new.fnv1a_32(value + seed) % 1000) / 1000.0 if version == 1
47
+ return (FNV.new.fnv1a_32(FNV.new.fnv1a_32(seed + value).to_s) % 10_000) / 10_000.0 if version == 2
45
48
 
46
- # Hash the user id and testName to a number from 0 to 1
47
- n = (FNV.new.fnv1a_32(userId + testId) % 1000) / 1000.0
48
-
49
- cumulativeWeight = 0
50
-
51
- match = -1
52
- i = 0
53
- weights.each do |weight|
54
- cumulativeWeight += weight
55
- if n < cumulativeWeight
56
- match = i
57
- break
58
- end
59
- i += 1
60
- end
61
-
62
- match
63
- end
64
-
65
- def self.hash(str)
66
- (FNV.new.fnv1a_32(str) % 1000) / 1000.0
49
+ -1
67
50
  end
68
51
 
69
- def self.in_namespace(userId, namespace)
70
- n = hash("#{userId}__#{namespace[0]}")
52
+ def self.in_namespace(hash_value, namespace)
53
+ n = hash(seed: "__#{namespace[0]}", value: hash_value, version: 1)
71
54
  n >= namespace[1] && n < namespace[2]
72
55
  end
73
56
 
74
- def self.get_equal_weights(numVariations)
75
- return [] if numVariations < 1
57
+ def self.get_equal_weights(num_variations)
58
+ return [] if num_variations < 1
76
59
 
77
60
  weights = []
78
- (1..numVariations).each do |_i|
79
- weights << (1.0 / numVariations)
61
+ (1..num_variations).each do |_i|
62
+ weights << (1.0 / num_variations)
80
63
  end
81
64
  weights
82
65
  end
83
66
 
84
67
  # Determine bucket ranges for experiment variations
85
- def self.get_bucket_ranges(numVariations, coverage = 1, weights = [])
68
+ def self.get_bucket_ranges(num_variations, coverage = 1, weights = [])
86
69
  # Make sure coverage is within bounds
87
70
  coverage = 1 if coverage.nil?
88
71
  coverage = 0 if coverage.negative?
89
72
  coverage = 1 if coverage > 1
90
73
 
91
74
  # Default to equal weights
92
- weights = get_equal_weights(numVariations) if !weights || weights.length != numVariations
75
+ weights = get_equal_weights(num_variations) if !weights || weights.length != num_variations
93
76
 
94
77
  # If weights don't add up to 1 (or close to it), default to equal weights
95
78
  total = weights.sum
96
- weights = get_equal_weights(numVariations) if total < 0.99 || total > 1.01
79
+ weights = get_equal_weights(num_variations) if total < 0.99 || total > 1.01
97
80
 
98
81
  # Convert weights to ranges
99
82
  cumulative = 0
@@ -101,23 +84,23 @@ module Growthbook
101
84
  weights.each do |w|
102
85
  start = cumulative
103
86
  cumulative += w
104
- ranges << [start, start + coverage * w]
87
+ ranges << [start, start + (coverage * w)]
105
88
  end
106
89
 
107
90
  ranges
108
91
  end
109
92
 
110
93
  # Chose a variation based on a hash and range
111
- def self.choose_variation(n, ranges)
94
+ def self.choose_variation(num, ranges)
112
95
  ranges.each_with_index do |range, i|
113
- return i if n >= range[0] && n < range[1]
96
+ return i if num >= range[0] && num < range[1]
114
97
  end
115
98
  -1
116
99
  end
117
100
 
118
101
  # Get an override variation from a url querystring
119
102
  # e.g. http://localhost?my-test=1 will return `1` for id `my-test`
120
- def self.get_query_string_override(id, url, numVariations)
103
+ def self.get_query_string_override(id, url, num_variations)
121
104
  # Skip if url is empty
122
105
  return nil if url == ''
123
106
 
@@ -132,7 +115,7 @@ module Growthbook
132
115
  return nil unless vals
133
116
 
134
117
  val = vals.last
135
- return nill unless val
118
+ return nil unless val
136
119
 
137
120
  # Parse the value as an integer
138
121
  n = begin
@@ -144,9 +127,13 @@ module Growthbook
144
127
  # Make sure the integer is within range
145
128
  return nil if n.nil?
146
129
  return nil if n.negative?
147
- return nil if n >= numVariations
130
+ return nil if n >= num_variations
148
131
 
149
132
  n
150
133
  end
134
+
135
+ def self.in_range?(num, range)
136
+ num >= range[0] && num < range[1]
137
+ end
151
138
  end
152
139
  end
data/lib/growthbook.rb CHANGED
@@ -1,18 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # The GrowthBook SDK
3
4
  module Growthbook
4
5
  end
5
6
 
6
- require 'growthbook/client'
7
7
  require 'growthbook/conditions'
8
8
  require 'growthbook/context'
9
- require 'growthbook/experiment'
10
- require 'growthbook/experiment_result'
11
9
  require 'growthbook/feature'
12
10
  require 'growthbook/feature_result'
13
11
  require 'growthbook/feature_rule'
14
12
  require 'growthbook/inline_experiment'
15
13
  require 'growthbook/inline_experiment_result'
16
- require 'growthbook/lookup_result'
17
- require 'growthbook/user'
18
14
  require 'growthbook/util'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: growthbook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GrowthBook
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-08 00:00:00.000000000 Z
11
+ date: 2023-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: simplecov
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.21'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.21'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov-shields-badge
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.0
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: fnv
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -45,29 +73,19 @@ extensions: []
45
73
  extra_rdoc_files: []
46
74
  files:
47
75
  - lib/growthbook.rb
48
- - lib/growthbook/client.rb
49
76
  - lib/growthbook/conditions.rb
50
77
  - lib/growthbook/context.rb
51
- - lib/growthbook/experiment.rb
52
- - lib/growthbook/experiment_result.rb
53
78
  - lib/growthbook/feature.rb
54
79
  - lib/growthbook/feature_result.rb
55
80
  - lib/growthbook/feature_rule.rb
56
81
  - lib/growthbook/inline_experiment.rb
57
82
  - lib/growthbook/inline_experiment_result.rb
58
- - lib/growthbook/lookup_result.rb
59
- - lib/growthbook/user.rb
60
83
  - lib/growthbook/util.rb
61
- - spec/cases.json
62
- - spec/client_spec.rb
63
- - spec/context_spec.rb
64
- - spec/json_spec.rb
65
- - spec/user_spec.rb
66
- - spec/util_spec.rb
67
84
  homepage: https://github.com/growthbook/growthbook-ruby
68
85
  licenses:
69
86
  - MIT
70
- metadata: {}
87
+ metadata:
88
+ rubygems_mfa_required: 'true'
71
89
  post_install_message:
72
90
  rdoc_options: []
73
91
  require_paths:
@@ -76,7 +94,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
94
  requirements:
77
95
  - - ">="
78
96
  - !ruby/object:Gem::Version
79
- version: '0'
97
+ version: 2.5.0
80
98
  required_rubygems_version: !ruby/object:Gem::Requirement
81
99
  requirements:
82
100
  - - ">="
@@ -87,10 +105,4 @@ rubygems_version: 3.1.2
87
105
  signing_key:
88
106
  specification_version: 4
89
107
  summary: GrowthBook SDK for Ruby
90
- test_files:
91
- - spec/json_spec.rb
92
- - spec/user_spec.rb
93
- - spec/util_spec.rb
94
- - spec/context_spec.rb
95
- - spec/client_spec.rb
96
- - spec/cases.json
108
+ test_files: []
@@ -1,67 +0,0 @@
1
- module Growthbook
2
- class Client
3
- # @returns [Boolean]
4
- attr_accessor :enabled
5
-
6
- # @returns [Array<Growthbook::Experiment>]
7
- attr_accessor :experiments
8
-
9
- # @param config [Hash]
10
- # @option config [Boolean] :enabled (true) Set to false to disable all experiments
11
- # @option config [Array<Growthbook::Experiment>] :experiments ([]) Array of Growthbook::Experiment objects
12
- def initialize(config = {})
13
- @enabled = config.has_key?(:enabled) ? config[:enabled] : true
14
- @experiments = config[:experiments] || []
15
- @resultsToTrack = []
16
- end
17
-
18
- # Look up a pre-configured experiment by id
19
- #
20
- # @param id [String] The experiment id to look up
21
- # @return [Growthbook::Experiment, nil] the experiment object or nil if not found
22
- def getExperiment(id)
23
- match = nil;
24
- @experiments.each do |exp|
25
- if exp.id == id
26
- match = exp
27
- break
28
- end
29
- end
30
- return match
31
- end
32
-
33
- # Get a User object you can run experiments against
34
- #
35
- # @param params [Hash]
36
- # @option params [String, nil] :id The logged-in user id
37
- # @option params [String, nil] :anonId The anonymous id (session id, ip address, cookie, etc.)
38
- # @option params [Hash, nil] :attributes Any user attributes you want to use for experiment targeting
39
- # Values can be any type, even nested arrays and hashes
40
- # @return [Growthbook::User] the User object
41
- def user(params = {})
42
- Growthbook::User.new(
43
- params[:anonId] || nil,
44
- params[:id] || nil,
45
- params[:attributes] || nil,
46
- self
47
- )
48
- end
49
-
50
- def importExperimentsHash(experimentsHash = {})
51
- @experiments = []
52
- experimentsHash.each do |id, data|
53
- variations = data["variations"]
54
-
55
- options = {}
56
- options[:coverage] = data["coverage"] if data.has_key?("coverage")
57
- options[:weights] = data["weights"] if data.has_key?("weights")
58
- options[:force] = data["force"] if data.has_key?("force")
59
- options[:anon] = data["anon"] if data.has_key?("anon")
60
- options[:targeting] = data["targeting"] if data.has_key?("targeting")
61
- options[:data] = data["data"] if data.has_key?("data")
62
-
63
- @experiments << Growthbook::Experiment.new(id, variations, options)
64
- end
65
- end
66
- end
67
- end
@@ -1,72 +0,0 @@
1
- module Growthbook
2
- class Experiment
3
- # @returns [String]
4
- attr_accessor :id
5
-
6
- # @returns [Integer]
7
- attr_accessor :variations
8
-
9
- # @returns [Float]
10
- attr_accessor :coverage
11
-
12
- # @returns [Array<Float>]
13
- attr_accessor :weights
14
-
15
- # @returns [Boolean]
16
- attr_accessor :anon
17
-
18
- # @returns [Array<String>]
19
- attr_accessor :targeting
20
-
21
- # @returns [Integer, nil]
22
- attr_accessor :force
23
-
24
- # @returns [Hash]
25
- attr_accessor :data
26
-
27
- # Constructor for an Experiment
28
- #
29
- # @param id [String] The unique id for this experiment
30
- # @param variations [Integer] The number of variations in this experiment (including the Control)
31
- # @param options [Hash]
32
- # @option options [Float] :coverage (1.0) The percent of elegible traffic to include in the experiment
33
- # @option options [Array<Float>] :weights The relative weights of the variations.
34
- # Length must be the same as the number of variations. Total should add to 1.0.
35
- # Default is an even split between variations
36
- # @option options [Boolean] :anon (false) If false, the experiment uses the logged-in user id for bucketing
37
- # If true, the experiment uses the anonymous id for bucketing
38
- # @option options [Array<String>] :targeting Array of targeting rules in the format "key op value"
39
- # where op is one of: =, !=, <, >, ~, !~
40
- # @option options [Integer, nil] :force If an integer, force all users to get this variation
41
- # @option options [Hash] :data Data to attach to the variations
42
- def initialize(id, variations, options = {})
43
- @id = id
44
- @variations = variations
45
- @coverage = options[:coverage] || 1
46
- @weights = options[:weights] || getEqualWeights()
47
- @force = options.has_key?(:force) ? options[:force] : nil
48
- @anon = options.has_key?(:anon) ? options[:anon] : false
49
- @targeting = options[:targeting] || []
50
- @data = options[:data] || {}
51
- end
52
-
53
- def getScaledWeights
54
- scaled = @weights.map do |n|
55
- n*@coverage
56
- end
57
-
58
- return scaled
59
- end
60
-
61
- private
62
-
63
- def getEqualWeights
64
- weights = []
65
- n = @variations
66
- for i in 1..n
67
- weights << (1.0 / n)
68
- end
69
- return weights
70
- end
71
- end
72
- end