growthbook 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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