optimizely-sdk 0.0.12
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 +7 -0
- data/lib/optimizely.rb +179 -0
- data/lib/optimizely/audience.rb +37 -0
- data/lib/optimizely/bucketer.rb +143 -0
- data/lib/optimizely/condition.rb +118 -0
- data/lib/optimizely/error_handler.rb +24 -0
- data/lib/optimizely/event_builder.rb +200 -0
- data/lib/optimizely/event_dispatcher.rb +18 -0
- data/lib/optimizely/exceptions.rb +83 -0
- data/lib/optimizely/helpers/constants.rb +173 -0
- data/lib/optimizely/helpers/group.rb +14 -0
- data/lib/optimizely/helpers/validator.rb +60 -0
- data/lib/optimizely/logger.rb +30 -0
- data/lib/optimizely/params.rb +14 -0
- data/lib/optimizely/project_config.rb +255 -0
- data/lib/optimizely/version.rb +3 -0
- data/lib/start.rb +6 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 901e065c0246efcd95969e7b04b84dc9aab2d9e7
|
4
|
+
data.tar.gz: c7a3e774508632a1161d827178e49ffa039d560f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4ff15bc14a26529b3332e41b772befc96559e0d9230d3d41c7c198f757f2a7aede6ec5030064421a9cb87ca676f76d997310ac8211af9fc36554713eb262413c
|
7
|
+
data.tar.gz: 9b6ccd16b467ee4d4f2092e775ef73218fa6ac44f70db80f670884c74aebae09f9623353431806d0f2620e08d4471f6d47cf522d1a32274ac046e57286668819
|
data/lib/optimizely.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require_relative 'optimizely/audience'
|
2
|
+
require_relative 'optimizely/bucketer'
|
3
|
+
require_relative 'optimizely/error_handler'
|
4
|
+
require_relative 'optimizely/event_builder'
|
5
|
+
require_relative 'optimizely/event_dispatcher'
|
6
|
+
require_relative 'optimizely/exceptions'
|
7
|
+
require_relative 'optimizely/helpers/group'
|
8
|
+
require_relative 'optimizely/helpers/validator'
|
9
|
+
require_relative 'optimizely/logger'
|
10
|
+
require_relative 'optimizely/project_config'
|
11
|
+
|
12
|
+
module Optimizely
|
13
|
+
class Project
|
14
|
+
attr_accessor :config
|
15
|
+
attr_accessor :bucketer
|
16
|
+
attr_accessor :event_builder
|
17
|
+
attr_accessor :event_dispatcher
|
18
|
+
attr_accessor :logger
|
19
|
+
attr_accessor :error_handler
|
20
|
+
|
21
|
+
def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil)
|
22
|
+
# Constructor for Projects.
|
23
|
+
#
|
24
|
+
# datafile - JSON string representing the project.
|
25
|
+
# event_dispatcher - Provides a dispatch_event method which if given a URL and params sends a request to it.
|
26
|
+
# logger - Optional param which provides a log method to log messages. By default nothing would be logged.
|
27
|
+
# error_handler - Optional param which provides a handle_error method to handle exceptions.
|
28
|
+
# By default all exceptions will be suppressed.
|
29
|
+
|
30
|
+
@logger = logger || NoOpLogger.new
|
31
|
+
@error_handler = error_handler || NoOpErrorHandler.new
|
32
|
+
@event_dispatcher = event_dispatcher || EventDispatcher.new
|
33
|
+
validate_inputs(datafile)
|
34
|
+
|
35
|
+
@config = ProjectConfig.new(datafile, @logger, @error_handler)
|
36
|
+
@bucketer = Bucketer.new(@config)
|
37
|
+
@event_builder = EventBuilder.new(@config, @bucketer)
|
38
|
+
end
|
39
|
+
|
40
|
+
def activate(experiment_key, user_id, attributes = nil)
|
41
|
+
# Buckets visitor and sends impression event to Optimizely.
|
42
|
+
#
|
43
|
+
# experiment_key - Experiment which needs to be activated.
|
44
|
+
# user_id - String ID for user.
|
45
|
+
# attributes - Hash representing user attributes and values to be recorded.
|
46
|
+
#
|
47
|
+
# Returns variation key representing the variation the user will be bucketed in.
|
48
|
+
# Returns nil if experiment is not Running or if user is not in experiment.
|
49
|
+
|
50
|
+
if attributes && !attributes_valid?(attributes)
|
51
|
+
@logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
|
55
|
+
unless preconditions_valid?(experiment_key, user_id, attributes)
|
56
|
+
@logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
|
60
|
+
variation_id = @bucketer.bucket(experiment_key, user_id)
|
61
|
+
|
62
|
+
if not variation_id
|
63
|
+
@logger.log(Logger::INFO, "Not activating user '#{user_id}'.")
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# Create and dispatch impression event
|
68
|
+
impression_event = @event_builder.create_impression_event(experiment_key, variation_id, user_id, attributes)
|
69
|
+
@logger.log(Logger::INFO,
|
70
|
+
'Dispatching impression event to URL %s with params %s.' % [impression_event.url,
|
71
|
+
impression_event.params])
|
72
|
+
@event_dispatcher.dispatch_event(impression_event.url, impression_event.params)
|
73
|
+
|
74
|
+
@config.get_variation_key_from_id(experiment_key, variation_id)
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_variation(experiment_key, user_id, attributes = nil)
|
78
|
+
# Gets variation where visitor will be bucketed.
|
79
|
+
#
|
80
|
+
# experiment_key - Experiment for which visitor variation needs to be determined.
|
81
|
+
# user_id - String ID for user.
|
82
|
+
# attributes - Hash representing user attributes.
|
83
|
+
#
|
84
|
+
# Returns variation key where visitor will be bucketed.
|
85
|
+
# Returns nil if experiment is not Running or if user is not in experiment.
|
86
|
+
|
87
|
+
if attributes && !attributes_valid?(attributes)
|
88
|
+
@logger.log(Logger::INFO, "Not activating user '#{user_id}.")
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
|
92
|
+
unless preconditions_valid?(experiment_key, user_id, attributes)
|
93
|
+
@logger.log(Logger::INFO, "Not activating user '#{user_id}.")
|
94
|
+
return nil
|
95
|
+
end
|
96
|
+
|
97
|
+
variation_id = @bucketer.bucket(experiment_key, user_id)
|
98
|
+
@config.get_variation_key_from_id(experiment_key, variation_id)
|
99
|
+
end
|
100
|
+
|
101
|
+
def track(event_key, user_id, attributes = nil, event_value = nil)
|
102
|
+
# Send conversion event to Optimizely.
|
103
|
+
#
|
104
|
+
# event_key - Goal key representing the event which needs to be recorded.
|
105
|
+
# user_id - String ID for user.
|
106
|
+
# attributes - Hash representing visitor attributes and values which need to be recorded.
|
107
|
+
# event_value - Value associated with the event. Can be used to represent revenue in cents.
|
108
|
+
|
109
|
+
# Create and dispatch conversion event
|
110
|
+
|
111
|
+
return nil if attributes && !attributes_valid?(attributes)
|
112
|
+
|
113
|
+
experiment_ids = @config.get_experiment_ids_for_goal(event_key)
|
114
|
+
if experiment_ids.empty?
|
115
|
+
@config.logger.log(Logger::INFO, "Not tracking user '#{user_id}' for experiment '#{experiment_key}'.")
|
116
|
+
return nil
|
117
|
+
end
|
118
|
+
|
119
|
+
# Filter out experiments that are not running or that do not include the user in audience conditions
|
120
|
+
valid_experiment_keys = []
|
121
|
+
experiment_ids.each do |experiment_id|
|
122
|
+
experiment_key = @config.experiment_id_map[experiment_id]['key']
|
123
|
+
unless preconditions_valid?(experiment_key, user_id, attributes)
|
124
|
+
@config.logger.log(Logger::INFO, "Not tracking user '#{user_id}' for experiment '#{experiment_key}'.")
|
125
|
+
next
|
126
|
+
end
|
127
|
+
valid_experiment_keys.push(experiment_key)
|
128
|
+
end
|
129
|
+
|
130
|
+
conversion_event = @event_builder.create_conversion_event(event_key, user_id, attributes,
|
131
|
+
event_value, valid_experiment_keys)
|
132
|
+
@logger.log(Logger::INFO,
|
133
|
+
'Dispatching conversion event to URL %s with params %s.' % [conversion_event.url,
|
134
|
+
conversion_event.params])
|
135
|
+
@event_dispatcher.dispatch_event(conversion_event.url, conversion_event.params)
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def preconditions_valid?(experiment_key, user_id, attributes)
|
141
|
+
# Validates preconditions for bucketing a user.
|
142
|
+
#
|
143
|
+
# experiment_key - String key for an experiment.
|
144
|
+
# user_id - String ID of user.
|
145
|
+
# attributes - Hash of user attributes.
|
146
|
+
#
|
147
|
+
# Returns boolean representing whether all preconditions are valid.
|
148
|
+
|
149
|
+
unless @config.experiment_running?(experiment_key)
|
150
|
+
@logger.log(Logger::INFO, "Experiment '#{experiment_key} is not running.")
|
151
|
+
return false
|
152
|
+
end
|
153
|
+
|
154
|
+
unless Audience.user_in_experiment?(@config, experiment_key, attributes)
|
155
|
+
@logger.log(Logger::INFO,
|
156
|
+
"User '#{user_id} does not meet the conditions to be in experiment '#{experiment_key}.")
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
|
160
|
+
true
|
161
|
+
end
|
162
|
+
|
163
|
+
def attributes_valid?(attributes)
|
164
|
+
unless Helpers::Validator.attributes_valid?(attributes)
|
165
|
+
@logger.log(Logger::ERROR, 'Provided attributes are in an invalid format.')
|
166
|
+
@error_handler.handle_error(InvalidAttributeFormatError)
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
true
|
170
|
+
end
|
171
|
+
|
172
|
+
def validate_inputs(datafile)
|
173
|
+
raise InvalidDatafileError unless Helpers::Validator.datafile_valid?(datafile)
|
174
|
+
raise InvalidLoggerError unless Helpers::Validator.logger_valid?(@logger)
|
175
|
+
raise InvalidErrorHandlerError unless Helpers::Validator.error_handler_valid?(@error_handler)
|
176
|
+
raise InvalidEventDispatcherError unless Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative './condition'
|
3
|
+
|
4
|
+
module Optimizely
|
5
|
+
module Audience
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def user_in_experiment?(config, experiment_key, attributes)
|
9
|
+
# Determine for given experiment if user satisfies the audiences for the experiment.
|
10
|
+
#
|
11
|
+
# config - Representation of the Optimizely project config.
|
12
|
+
# experiment_key - Key representing experiment for which visitor is to be bucketed.
|
13
|
+
# attributes - Hash representing user attributes which will be used in determining if
|
14
|
+
# the audience conditions are met.
|
15
|
+
#
|
16
|
+
# Returns boolean representing if user satisfies audience conditions for any of the audiences or not.
|
17
|
+
|
18
|
+
audience_ids = config.get_audience_ids_for_experiment(experiment_key)
|
19
|
+
|
20
|
+
# Return true if there are no audiences
|
21
|
+
return true if audience_ids.empty?
|
22
|
+
|
23
|
+
# Return false if there are audiences but no attributes
|
24
|
+
return false unless attributes
|
25
|
+
|
26
|
+
# Return true if any one of the audience conditions are met
|
27
|
+
@condition_evaluator = ConditionEvaluator.new(attributes)
|
28
|
+
audience_ids.each do |audience_id|
|
29
|
+
audience_conditions = config.get_audience_conditions_from_id(audience_id)
|
30
|
+
audience_conditions = JSON.load(audience_conditions)
|
31
|
+
return true if @condition_evaluator.evaluate(audience_conditions)
|
32
|
+
end
|
33
|
+
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'murmurhash3'
|
2
|
+
require_relative 'helpers/group'
|
3
|
+
|
4
|
+
module Optimizely
|
5
|
+
class Bucketer
|
6
|
+
# Optimizely bucketing algorithm that evenly distributes visitors.
|
7
|
+
|
8
|
+
BUCKETING_ID_TEMPLATE = '%{user_id}%{entity_id}'
|
9
|
+
HASH_SEED = 1
|
10
|
+
MAX_HASH_VALUE = 2**32
|
11
|
+
MAX_TRAFFIC_VALUE = 10_000
|
12
|
+
UNSIGNED_MAX_32_BIT_VALUE = 0xFFFFFFFF
|
13
|
+
|
14
|
+
def initialize(config)
|
15
|
+
# Bucketer init method to set bucketing seed and project config data.
|
16
|
+
#
|
17
|
+
# config - ProjectConfig data to be used in making bucketing decisions.
|
18
|
+
|
19
|
+
@bucket_seed = HASH_SEED
|
20
|
+
@config = config
|
21
|
+
end
|
22
|
+
|
23
|
+
def bucket(experiment_key, user_id)
|
24
|
+
# Determines ID of variation to be shown for a given experiment key and user ID.
|
25
|
+
#
|
26
|
+
# experiment_key - String Key representing experiment for which visitor is to be bucketed.
|
27
|
+
# user_id - String ID for user.
|
28
|
+
#
|
29
|
+
# Returns String variation ID in which visitor with ID user_id has been placed. Nil if no variation.
|
30
|
+
|
31
|
+
# handle forced variations if applicable
|
32
|
+
forced_variations = @config.get_forced_variations(experiment_key)
|
33
|
+
forced_variation_key = forced_variations[user_id]
|
34
|
+
if forced_variation_key
|
35
|
+
forced_variation_id = @config.get_variation_id_from_key(experiment_key, forced_variation_key)
|
36
|
+
if forced_variation_id
|
37
|
+
@config.logger.log(Logger::INFO, "User '#{user_id}' is forced in variation '#{forced_variation_key}'.")
|
38
|
+
return forced_variation_id
|
39
|
+
else
|
40
|
+
@config.logger.log(
|
41
|
+
Logger::INFO,
|
42
|
+
"Variation key '#{forced_variation_key}' is not in datafile. Not activating user '#{user_id}'."
|
43
|
+
)
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# check if experiment is in a group; if so, check if user is bucketed into specified experiment
|
49
|
+
experiment_id = @config.get_experiment_id(experiment_key)
|
50
|
+
group_id = @config.get_experiment_group_id(experiment_key)
|
51
|
+
if group_id
|
52
|
+
group = @config.group_key_map.fetch(group_id)
|
53
|
+
if Helpers::Group.random_policy?(group)
|
54
|
+
bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: group_id)
|
55
|
+
traffic_allocations = group.fetch('trafficAllocation')
|
56
|
+
bucket_value = generate_bucket_value(bucketing_id)
|
57
|
+
@config.logger.log(Logger::DEBUG, "Assigned experiment bucket #{bucket_value} to user '#{user_id}'.")
|
58
|
+
bucketed_experiment_id = find_bucket(bucket_value, traffic_allocations)
|
59
|
+
|
60
|
+
# return if the user is not bucketed into any experiment
|
61
|
+
unless bucketed_experiment_id
|
62
|
+
@config.logger.log(Logger::INFO, "User '#{user_id}' is in no experiment.")
|
63
|
+
return nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# return if the user is bucketed into a different experiment than the one specified
|
67
|
+
if bucketed_experiment_id != experiment_id
|
68
|
+
@config.logger.log(
|
69
|
+
Logger::INFO,
|
70
|
+
"User '#{user_id}' is not in experiment '#{experiment_key}' of group #{group_id}."
|
71
|
+
)
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# continue bucketing if the user is bucketed into the experiment specified
|
76
|
+
@config.logger.log(
|
77
|
+
Logger::INFO,
|
78
|
+
"User '#{user_id}' is in experiment '#{experiment_key}' of group #{group_id}."
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: experiment_id)
|
84
|
+
bucket_value = generate_bucket_value(bucketing_id)
|
85
|
+
@config.logger.log(Logger::DEBUG, "Assigned variation bucket #{bucket_value} to user '#{user_id}'.")
|
86
|
+
traffic_allocations = @config.get_traffic_allocation(experiment_key)
|
87
|
+
variation_id = find_bucket(bucket_value, traffic_allocations)
|
88
|
+
if variation_id
|
89
|
+
variation_key = @config.get_variation_key_from_id(experiment_key, variation_id)
|
90
|
+
@config.logger.log(
|
91
|
+
Logger::INFO,
|
92
|
+
"User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
|
93
|
+
)
|
94
|
+
return variation_id
|
95
|
+
end
|
96
|
+
|
97
|
+
@config.logger.log(Logger::INFO, "User '#{user_id}' is in no variation.")
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def find_bucket(bucket_value, traffic_allocations)
|
104
|
+
# Helper function to find the matching entity ID for a given bucketing value in a list of traffic allocations.
|
105
|
+
#
|
106
|
+
# bucket_value - Integer bucket value
|
107
|
+
# traffic_allocations - Array of traffic allocations
|
108
|
+
#
|
109
|
+
# Returns entity ID corresponding to the provided bucket value or nil if no match is found.
|
110
|
+
|
111
|
+
traffic_allocations.each do |traffic_allocation|
|
112
|
+
current_end_of_range = traffic_allocation['endOfRange']
|
113
|
+
if bucket_value < current_end_of_range
|
114
|
+
entity_id = traffic_allocation['entityId']
|
115
|
+
return entity_id
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def generate_bucket_value(bucketing_id)
|
123
|
+
# Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE).
|
124
|
+
#
|
125
|
+
# bucketing_id - String ID for bucketing.
|
126
|
+
#
|
127
|
+
# Returns bucket value corresponding to the provided bucketing ID.
|
128
|
+
|
129
|
+
ratio = (generate_unsigned_hash_code_32_bit(bucketing_id)).to_f / MAX_HASH_VALUE
|
130
|
+
(ratio * MAX_TRAFFIC_VALUE).to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
def generate_unsigned_hash_code_32_bit(bucketing_id)
|
134
|
+
# Helper function to retreive hash code
|
135
|
+
#
|
136
|
+
# bucketing_id - String ID for bucketing.
|
137
|
+
#
|
138
|
+
# Returns hash code which is a 32 bit unsigned integer.
|
139
|
+
|
140
|
+
MurmurHash3::V32.str_hash(bucketing_id, @bucket_seed) & UNSIGNED_MAX_32_BIT_VALUE
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Optimizely
|
4
|
+
class ConditionalOperatorTypes
|
5
|
+
AND = 'and'
|
6
|
+
OR = 'or'
|
7
|
+
NOT = 'not'
|
8
|
+
end
|
9
|
+
|
10
|
+
class ConditionEvaluator
|
11
|
+
DEFAULT_OPERATOR_TYPES = [
|
12
|
+
ConditionalOperatorTypes::AND,
|
13
|
+
ConditionalOperatorTypes::OR,
|
14
|
+
ConditionalOperatorTypes::NOT
|
15
|
+
]
|
16
|
+
|
17
|
+
attr_reader :user_attributes
|
18
|
+
|
19
|
+
def initialize(user_attributes)
|
20
|
+
@user_attributes = user_attributes
|
21
|
+
end
|
22
|
+
|
23
|
+
def and_evaluator(conditions)
|
24
|
+
# Evaluates an array of conditions as if the evaluator had been applied
|
25
|
+
# to each entry and the results AND-ed together.
|
26
|
+
#
|
27
|
+
# conditions - Array of conditions ex: [operand_1, operand_2]
|
28
|
+
#
|
29
|
+
# Returns boolean true if all operands evaluate to true.
|
30
|
+
|
31
|
+
conditions.each do |condition|
|
32
|
+
result = evaluate(condition)
|
33
|
+
return result if result == false
|
34
|
+
end
|
35
|
+
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def or_evaluator(conditions)
|
40
|
+
# Evaluates an array of conditions as if the evaluator had been applied
|
41
|
+
# to each entry and the results AND-ed together.
|
42
|
+
#
|
43
|
+
# conditions - Array of conditions ex: [operand_1, operand_2]
|
44
|
+
#
|
45
|
+
# Returns boolean true if any operand evaluates to true.
|
46
|
+
|
47
|
+
conditions.each do |condition|
|
48
|
+
result = evaluate(condition)
|
49
|
+
return result if result == true
|
50
|
+
end
|
51
|
+
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def not_evaluator(single_condition)
|
56
|
+
# Evaluates an array of conditions as if the evaluator had been applied
|
57
|
+
# to a single entry and NOT was applied to the result.
|
58
|
+
#
|
59
|
+
# single_condition - Array of a single condition ex: [operand_1]
|
60
|
+
#
|
61
|
+
# Returns boolean true if the operand evaluates to false.
|
62
|
+
|
63
|
+
return false if single_condition.length != 1
|
64
|
+
|
65
|
+
!evaluate(single_condition[0])
|
66
|
+
end
|
67
|
+
|
68
|
+
def evaluator(condition_array)
|
69
|
+
# Method to compare single audience condition against provided user data i.e. attributes.
|
70
|
+
#
|
71
|
+
# condition_array - Array consisting of condition key and corresponding value.
|
72
|
+
#
|
73
|
+
# Returns boolean indicating the result of comparing the condition value against the user attributes.
|
74
|
+
|
75
|
+
condition_array[1] == @user_attributes[condition_array[0]]
|
76
|
+
end
|
77
|
+
|
78
|
+
def evaluate(conditions)
|
79
|
+
# Top level method to evaluate audience conditions.
|
80
|
+
#
|
81
|
+
# conditions - Nested array of and/or conditions.
|
82
|
+
# Example: ['and', operand_1, ['or', operand_2, operand_3]]
|
83
|
+
#
|
84
|
+
# Returns boolean result of evaluating the conditions evaluated.
|
85
|
+
|
86
|
+
if conditions.is_a? Array
|
87
|
+
operator_type = conditions[0]
|
88
|
+
return false unless DEFAULT_OPERATOR_TYPES.include?(operator_type)
|
89
|
+
case operator_type
|
90
|
+
when ConditionalOperatorTypes::AND
|
91
|
+
return and_evaluator(conditions[1..-1])
|
92
|
+
when ConditionalOperatorTypes::OR
|
93
|
+
return or_evaluator(conditions[1..-1])
|
94
|
+
when ConditionalOperatorTypes::NOT
|
95
|
+
return not_evaluator(conditions[1..-1])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Create array of condition key and corresponding value of audience condition.
|
100
|
+
condition_array = audience_condition_deserializer(conditions)
|
101
|
+
|
102
|
+
# Compare audience condition against provided user data i.e. attributes.
|
103
|
+
evaluator(condition_array)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def audience_condition_deserializer(condition)
|
109
|
+
# Deserializer defining how hashes need to be decoded for audience conditions.
|
110
|
+
#
|
111
|
+
# condition - Hash representing one audience condition.
|
112
|
+
#
|
113
|
+
# Returns array consisting of condition key and corresponding value.
|
114
|
+
|
115
|
+
[condition['name'], condition['value']]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|