hackle-ruby-sdk 1.0.0 → 2.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hackle/client.rb +186 -87
  3. data/lib/hackle/config.rb +59 -17
  4. data/lib/hackle/decision.rb +113 -0
  5. data/lib/hackle/event.rb +89 -0
  6. data/lib/hackle/internal/clock/clock.rb +47 -0
  7. data/lib/hackle/internal/concurrent/executors.rb +20 -0
  8. data/lib/hackle/internal/concurrent/schedule/scheduler.rb +12 -0
  9. data/lib/hackle/internal/concurrent/schedule/timer_scheduler.rb +30 -0
  10. data/lib/hackle/internal/config/parameter_config.rb +50 -0
  11. data/lib/hackle/internal/core/hackle_core.rb +182 -0
  12. data/lib/hackle/{decision → internal/evaluation/bucketer}/bucketer.rb +17 -15
  13. data/lib/hackle/internal/evaluation/evaluator/contextual/contextual_evaluator.rb +29 -0
  14. data/lib/hackle/internal/evaluation/evaluator/delegating/delegating_evaluator.rb +26 -0
  15. data/lib/hackle/internal/evaluation/evaluator/evaluator.rb +117 -0
  16. data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_evaluation_flow_factory.rb +67 -0
  17. data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_evaluator.rb +172 -0
  18. data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_flow_evaluator.rb +241 -0
  19. data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_resolver.rb +166 -0
  20. data/lib/hackle/internal/evaluation/evaluator/remoteconfig/remote_config_determiner.rb +48 -0
  21. data/lib/hackle/internal/evaluation/evaluator/remoteconfig/remote_config_evaluator.rb +174 -0
  22. data/lib/hackle/internal/evaluation/flow/evaluation_flow.rb +49 -0
  23. data/lib/hackle/internal/evaluation/flow/flow_evaluator.rb +11 -0
  24. data/lib/hackle/internal/evaluation/match/condition/condition_matcher.rb +11 -0
  25. data/lib/hackle/internal/evaluation/match/condition/condition_matcher_factory.rb +53 -0
  26. data/lib/hackle/internal/evaluation/match/condition/experiment/experiment_condition_matcher.rb +29 -0
  27. data/lib/hackle/internal/evaluation/match/condition/experiment/experiment_evaluator_matcher.rb +135 -0
  28. data/lib/hackle/internal/evaluation/match/condition/segment/segment_condition_matcher.rb +67 -0
  29. data/lib/hackle/internal/evaluation/match/condition/user/user_condition_matcher.rb +44 -0
  30. data/lib/hackle/internal/evaluation/match/operator/operator_matcher.rb +185 -0
  31. data/lib/hackle/internal/evaluation/match/operator/operator_matcher_factory.rb +31 -0
  32. data/lib/hackle/internal/evaluation/match/target/target_matcher.rb +31 -0
  33. data/lib/hackle/internal/evaluation/match/value/value_matcher.rb +96 -0
  34. data/lib/hackle/internal/evaluation/match/value/value_matcher_factory.rb +28 -0
  35. data/lib/hackle/internal/evaluation/match/value/value_operator_matcher.rb +59 -0
  36. data/lib/hackle/internal/event/user_event.rb +187 -0
  37. data/lib/hackle/internal/event/user_event_dispatcher.rb +156 -0
  38. data/lib/hackle/internal/event/user_event_factory.rb +58 -0
  39. data/lib/hackle/internal/event/user_event_processor.rb +181 -0
  40. data/lib/hackle/internal/http/http.rb +28 -0
  41. data/lib/hackle/internal/http/http_client.rb +48 -0
  42. data/lib/hackle/internal/identifiers/identifier_builder.rb +67 -0
  43. data/lib/hackle/internal/logger/logger.rb +31 -0
  44. data/lib/hackle/internal/model/action.rb +57 -0
  45. data/lib/hackle/internal/model/bucket.rb +58 -0
  46. data/lib/hackle/internal/model/container.rb +47 -0
  47. data/lib/hackle/internal/model/decision_reason.rb +31 -0
  48. data/lib/hackle/{models → internal/model}/event_type.rb +5 -8
  49. data/lib/hackle/internal/model/experiment.rb +194 -0
  50. data/lib/hackle/internal/model/parameter_configuration.rb +19 -0
  51. data/lib/hackle/internal/model/remote_config_parameter.rb +76 -0
  52. data/lib/hackle/internal/model/sdk.rb +23 -0
  53. data/lib/hackle/internal/model/segment.rb +61 -0
  54. data/lib/hackle/internal/model/target.rb +203 -0
  55. data/lib/hackle/internal/model/target_rule.rb +19 -0
  56. data/lib/hackle/internal/model/targeting.rb +45 -0
  57. data/lib/hackle/internal/model/value_type.rb +75 -0
  58. data/lib/hackle/internal/model/variation.rb +27 -0
  59. data/lib/hackle/internal/model/version.rb +153 -0
  60. data/lib/hackle/internal/properties/properties_builder.rb +101 -0
  61. data/lib/hackle/internal/user/hackle_user.rb +74 -0
  62. data/lib/hackle/internal/user/hackle_user_resolver.rb +27 -0
  63. data/lib/hackle/internal/workspace/http_workspace_fetcher.rb +50 -0
  64. data/lib/hackle/internal/workspace/polling_workspace_fetcher.rb +62 -0
  65. data/lib/hackle/internal/workspace/workspace.rb +353 -0
  66. data/lib/hackle/internal/workspace/workspace_fetcher.rb +18 -0
  67. data/lib/hackle/remote_config.rb +55 -0
  68. data/lib/hackle/user.rb +124 -0
  69. data/lib/hackle/version.rb +1 -11
  70. data/lib/hackle.rb +4 -69
  71. metadata +123 -53
  72. data/.gitignore +0 -11
  73. data/.rspec +0 -2
  74. data/.travis.yml +0 -7
  75. data/Gemfile +0 -6
  76. data/README.md +0 -33
  77. data/Rakefile +0 -6
  78. data/hackle-ruby-sdk.gemspec +0 -29
  79. data/lib/hackle/decision/decider.rb +0 -69
  80. data/lib/hackle/events/event_dispatcher.rb +0 -96
  81. data/lib/hackle/events/event_processor.rb +0 -126
  82. data/lib/hackle/events/user_event.rb +0 -61
  83. data/lib/hackle/http/http.rb +0 -37
  84. data/lib/hackle/models/bucket.rb +0 -26
  85. data/lib/hackle/models/event.rb +0 -26
  86. data/lib/hackle/models/experiment.rb +0 -69
  87. data/lib/hackle/models/slot.rb +0 -22
  88. data/lib/hackle/models/user.rb +0 -24
  89. data/lib/hackle/models/variation.rb +0 -21
  90. data/lib/hackle/workspaces/http_workspace_fetcher.rb +0 -24
  91. data/lib/hackle/workspaces/polling_workspace_fetcher.rb +0 -47
  92. data/lib/hackle/workspaces/workspace.rb +0 -100
@@ -0,0 +1,353 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hackle/internal/model/action'
4
+ require 'hackle/internal/model/bucket'
5
+ require 'hackle/internal/model/container'
6
+ require 'hackle/internal/model/event_type'
7
+ require 'hackle/internal/model/experiment'
8
+ require 'hackle/internal/model/parameter_configuration'
9
+ require 'hackle/internal/model/remote_config_parameter'
10
+ require 'hackle/internal/model/segment'
11
+ require 'hackle/internal/model/target'
12
+ require 'hackle/internal/model/target_rule'
13
+ require 'hackle/internal/model/targeting'
14
+ require 'hackle/internal/model/value_type'
15
+ require 'hackle/internal/model/variation'
16
+
17
+ module Hackle
18
+ class Workspace
19
+ # @param experiments [Hash{Integer => Experiment}]
20
+ # @param feature_flags [Hash{Integer => Experiment}]
21
+ # @param buckets [Hash{Integer => Bucket}]
22
+ # @param event_types [Hash{String => EventType}]
23
+ # @param segments [Hash{String => Segment}]
24
+ # @param containers [Hash{Integer => Container}]
25
+ # @param parameter_configurations [Hash{Integer => ParameterConfiguration}]
26
+ # @param remote_config_parameters [Hash{String => RemoteConfigParameter}]
27
+ def initialize(
28
+ experiments:,
29
+ feature_flags:,
30
+ buckets:,
31
+ event_types:,
32
+ segments:,
33
+ containers:,
34
+ parameter_configurations:,
35
+ remote_config_parameters:
36
+ )
37
+ # @type [Hash{Integer => Experiment}]
38
+ @experiments = experiments
39
+
40
+ # @type [Hash{Integer => Experiment}]
41
+ @feature_flags = feature_flags
42
+
43
+ # @type [Hash{Integer => Bucket}]
44
+ @buckets = buckets
45
+
46
+ # @type [Hash{String => EventType}]
47
+ @event_types = event_types
48
+
49
+ # @type [Hash{String => Segment}]
50
+ @segments = segments
51
+
52
+ # @type [Hash{Integer => Container}]
53
+ @containers = containers
54
+
55
+ # @type [Hash{Integer => ParameterConfiguration}]
56
+ @parameter_configurations = parameter_configurations
57
+
58
+ # @type [Hash{String => RemoteConfigParameter}]
59
+ @remote_config_parameters = remote_config_parameters
60
+ end
61
+
62
+ # @param experiment_key [Integer]
63
+ # @return [Hackle::Experiment, nil]
64
+ def get_experiment_or_nil(experiment_key)
65
+ @experiments[experiment_key]
66
+ end
67
+
68
+ # @param feature_key [Integer]
69
+ # @return [Hackle::Experiment, nil]
70
+ def get_feature_flag_or_nil(feature_key)
71
+ @feature_flags[feature_key]
72
+ end
73
+
74
+ # @param event_type_key [String]
75
+ # @return [Hackle::EventType, nil]
76
+ def get_event_type_or_nil(event_type_key)
77
+ @event_types[event_type_key]
78
+ end
79
+
80
+ # @param bucket_id [Integer]
81
+ # @return [Hackle::Bucket, nil]
82
+ def get_bucket_or_nil(bucket_id)
83
+ @buckets[bucket_id]
84
+ end
85
+
86
+ # @param segment_key [String]
87
+ # @return [Hackle::Segment, nil]
88
+ def get_segment_or_nil(segment_key)
89
+ @segments[segment_key]
90
+ end
91
+
92
+ # @param container_id [Integer]
93
+ # @return [Hackle::Container, nil]
94
+ def get_container_or_nil(container_id)
95
+ @containers[container_id]
96
+ end
97
+
98
+ # @param parameter_configuration_id [Integer]
99
+ # @return [Hackle::ParameterConfiguration, nil]
100
+ def get_parameter_configuration_or_nil(parameter_configuration_id)
101
+ @parameter_configurations[parameter_configuration_id]
102
+ end
103
+
104
+ # @param parameter_key [String]
105
+ # @return [Hackle::RemoteConfigParameter, nil]
106
+ def get_remote_config_parameter_or_nil(parameter_key)
107
+ @remote_config_parameters[parameter_key]
108
+ end
109
+
110
+ class << self
111
+ # @param experiments [Array<Experiment>]
112
+ # @param feature_flags [Array<Experiment>]
113
+ # @param buckets [Array<Bucket>]
114
+ # @param event_types [Array<EventType>]
115
+ # @param segments [Array<Segment>]
116
+ # @param containers [Array<Container>]
117
+ # @param parameter_configurations [Array<ParameterConfiguration>]
118
+ # @param remote_config_parameters [Array<RemoteConfigParameter>]
119
+ # @return [Workspace]
120
+ def create(
121
+ experiments: [],
122
+ feature_flags: [],
123
+ buckets: [],
124
+ event_types: [],
125
+ segments: [],
126
+ containers: [],
127
+ parameter_configurations: [],
128
+ remote_config_parameters: []
129
+ )
130
+ Workspace.new(
131
+ experiments: experiments.each_with_object({}) { |item, hash| hash[item.key] = item },
132
+ feature_flags: feature_flags.each_with_object({}) { |item, hash| hash[item.key] = item },
133
+ buckets: buckets.each_with_object({}) { |item, hash| hash[item.id] = item },
134
+ event_types: event_types.each_with_object({}) { |item, hash| hash[item.key] = item },
135
+ segments: segments.each_with_object({}) { |item, hash| hash[item.key] = item },
136
+ containers: containers.each_with_object({}) { |item, hash| hash[item.id] = item },
137
+ parameter_configurations: parameter_configurations.each_with_object({}) { |item, hash| hash[item.id] = item },
138
+ remote_config_parameters: remote_config_parameters.each_with_object({}) { |item, hash| hash[item.key] = item }
139
+ )
140
+ end
141
+
142
+ # @param hash [Hash]
143
+ # @return [Hackle::Workspace]
144
+ def from_hash(hash)
145
+ Workspace.create(
146
+ experiments: hash[:experiments].map { |it| experiment(it, ExperimentType::AB_TEST) },
147
+ feature_flags: hash[:featureFlags].map { |it| experiment(it, ExperimentType::FEATURE_FLAG) },
148
+ buckets: hash[:buckets].map { |it| bucket(it) },
149
+ event_types: hash[:events].map { |it| event_type(it) },
150
+ segments: hash[:segments].map { |it| segment_or_nil(it) }.compact,
151
+ containers: hash[:containers].map { |it| container(it) },
152
+ parameter_configurations: hash[:parameterConfigurations].map { |it| parameter_configuration(it) },
153
+ remote_config_parameters: hash[:remoteConfigParameters].map { |it| remote_config_parameter_or_nil(it) }.compact
154
+ )
155
+ end
156
+
157
+ private
158
+
159
+ def experiment(hash, experiment_type)
160
+ status = ExperimentStatus.from_or_nil(hash[:execution][:status])
161
+ return nil if status.nil?
162
+
163
+ default_rule = action_or_nil(hash[:execution][:defaultRule])
164
+ return nil if default_rule.nil?
165
+
166
+ Experiment.new(
167
+ id: hash[:id],
168
+ key: hash[:key],
169
+ name: hash[:name],
170
+ type: experiment_type,
171
+ identifier_type: hash[:identifierType],
172
+ status: status,
173
+ version: hash[:version],
174
+ execution_version: hash[:execution][:version],
175
+ variations: hash[:variations].map { |it| variation(it) },
176
+ user_overrides: Hash[hash[:execution][:userOverrides].map { |it| [it[:userId], it[:variationId]] }],
177
+ segment_overrides: hash[:execution][:segmentOverrides].map { |it| target_rule_or_nil(it, TargetingType::IDENTIFIER) }.compact,
178
+ target_audiences: hash[:execution][:targetAudiences].map { |it| target_or_nil(it, TargetingType::PROPERTY) }.compact,
179
+ target_rules: hash[:execution][:targetRules].map { |it| target_rule_or_nil(it, TargetingType::PROPERTY) }.compact,
180
+ default_rule: default_rule,
181
+ container_id: hash[:containerId],
182
+ winner_variation_id: hash[:winnerVariationId]
183
+ )
184
+ end
185
+
186
+ def variation(hash)
187
+ Variation.new(
188
+ id: hash[:id],
189
+ key: hash[:key],
190
+ is_dropped: hash[:status] == 'DROPPED',
191
+ parameter_configuration_id: hash[:parameterConfigurationId]
192
+ )
193
+ end
194
+
195
+ def target_or_nil(hash, targeting_type)
196
+ conditions = hash[:conditions].map { |it| condition_or_nil(it, targeting_type) }.compact
197
+ return nil if conditions.empty?
198
+
199
+ Target.new(conditions: conditions)
200
+ end
201
+
202
+ def condition_or_nil(hash, targeting_type)
203
+ key = target_key_or_nil(hash[:key])
204
+ return nil if key.nil?
205
+ return nil unless targeting_type.supports?(key.type)
206
+
207
+ match = target_match_or_nil(hash[:match])
208
+ return nil if match.nil?
209
+
210
+ TargetCondition.new(
211
+ key: key,
212
+ match: match
213
+ )
214
+ end
215
+
216
+ def target_key_or_nil(hash)
217
+ type = TargetKeyType.from_or_nil(hash[:type])
218
+ return nil if type.nil?
219
+
220
+ TargetKey.new(
221
+ type: type,
222
+ name: hash[:name]
223
+ )
224
+ end
225
+
226
+ def target_match_or_nil(hash)
227
+ type = TargetMatchType.from_or_nil(hash[:type])
228
+ return nil if type.nil?
229
+
230
+ operator = TargetOperator.from_or_nil(hash[:operator])
231
+ return nil if operator.nil?
232
+
233
+ value_type = ValueType.from_or_nil(hash[:valueType])
234
+ return nil if value_type.nil?
235
+
236
+ TargetMatch.new(
237
+ type: type,
238
+ operator: operator,
239
+ value_type: value_type,
240
+ values: hash[:values]
241
+ )
242
+ end
243
+
244
+ def action_or_nil(hash)
245
+ type = ActionType.from_or_nil(hash[:type])
246
+ return nil if type.nil?
247
+
248
+ Action.new(
249
+ type: type,
250
+ variation_id: hash[:variationId],
251
+ bucket_id: hash[:bucketId]
252
+ )
253
+ end
254
+
255
+ def target_rule_or_nil(hash, targeting_type)
256
+ target = target_or_nil(hash[:target], targeting_type)
257
+ return nil if target.nil?
258
+
259
+ action = action_or_nil(hash[:action])
260
+ return nil if action.nil?
261
+
262
+ TargetRule.new(
263
+ target: target,
264
+ action: action
265
+ )
266
+ end
267
+
268
+ def bucket(hash)
269
+ Bucket.new(
270
+ id: hash[:id],
271
+ seed: hash[:seed],
272
+ slot_size: hash[:slotSize],
273
+ slots: hash[:slots].map { |it| slot(it) }
274
+ )
275
+ end
276
+
277
+ def slot(hash)
278
+ Slot.new(
279
+ start_inclusive: hash[:startInclusive],
280
+ end_exclusive: hash[:endExclusive],
281
+ variation_id: hash[:variationId]
282
+ )
283
+ end
284
+
285
+ def event_type(hash)
286
+ EventType.new(
287
+ id: hash[:id],
288
+ key: hash[:key]
289
+ )
290
+ end
291
+
292
+ def segment_or_nil(hash)
293
+ type = SegmentType.from_or_nil(hash[:type])
294
+ return nil if type.nil?
295
+
296
+ Segment.new(
297
+ id: hash[:id],
298
+ key: hash[:key],
299
+ type: type,
300
+ targets: hash[:targets].map { |it| target_or_nil(it, TargetingType::SEGMENT) }.compact
301
+ )
302
+ end
303
+
304
+ def container(hash)
305
+ Container.new(
306
+ id: hash[:id],
307
+ bucket_id: hash[:bucketId],
308
+ groups: hash[:groups].map { |it| ContainerGroup.new(id: it[:id], experiments: it[:experiments]) }
309
+ )
310
+ end
311
+
312
+ def parameter_configuration(hash)
313
+ ParameterConfiguration.new(
314
+ id: hash[:id],
315
+ parameters: Hash[hash[:parameters].map { |it| [it[:key], it[:value]] }]
316
+ )
317
+ end
318
+
319
+ def remote_config_parameter_or_nil(hash)
320
+ type = ValueType.from_or_nil(hash[:type])
321
+ return nil if type.nil?
322
+
323
+ RemoteConfigParameter.new(
324
+ id: hash[:id],
325
+ key: hash[:key],
326
+ type: type,
327
+ identifier_type: hash[:identifierType],
328
+ target_rules: hash[:targetRules].map { |it| remote_config_target_rule_or_nil(it) }.compact,
329
+ default_value: RemoteConfigValue.new(
330
+ id: hash[:defaultValue][:id],
331
+ raw_value: hash[:defaultValue][:value],
332
+ )
333
+ )
334
+ end
335
+
336
+ def remote_config_target_rule_or_nil(hash)
337
+ target = target_or_nil(hash[:target], TargetingType::PROPERTY)
338
+ return nil if target.nil?
339
+
340
+ RemoteConfigTargetRule.new(
341
+ key: hash[:key],
342
+ name: hash[:name],
343
+ target: target,
344
+ bucket_id: hash[:bucketId],
345
+ value: RemoteConfigValue.new(
346
+ id: hash[:value][:id],
347
+ raw_value: hash[:value][:value]
348
+ )
349
+ )
350
+ end
351
+ end
352
+ end
353
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hackle
4
+ module WorkspaceFetcher
5
+ # @return [Workspace, nil]
6
+ def fetch
7
+ end
8
+
9
+ def start
10
+ end
11
+
12
+ def stop
13
+ end
14
+
15
+ def resume
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hackle
4
+ class RemoteConfig
5
+ # @param user [Hackle::User]
6
+ # @param user_resolver [Hackle::HackleUserResolver]
7
+ # @param core [Hackle::Core]
8
+ def initialize(user:, user_resolver:, core:)
9
+ # @type [Hackle::User]
10
+ @user = user
11
+
12
+ # @type [Hackle::HackleUserResolver]
13
+ @user_resolver = user_resolver
14
+
15
+ # @type [Hackle::Core]
16
+ @core = core
17
+ end
18
+
19
+ # @param key [String]
20
+ # @param default_value [Object, nil]
21
+ # @return [Object, nil]
22
+ def get(key, default_value = nil)
23
+ case default_value
24
+ when nil
25
+ decision(key, default_value, ValueType::NULL).value
26
+ when String
27
+ decision(key, default_value, ValueType::STRING).value
28
+ when Numeric
29
+ decision(key, default_value, ValueType::NUMBER).value
30
+ when TrueClass, FalseClass
31
+ decision(key, default_value, ValueType::BOOLEAN).value
32
+ else
33
+ decision(key, default_value, ValueType::UNKNOWN).value
34
+ end
35
+
36
+ end
37
+
38
+ private
39
+
40
+ # @param key [String]
41
+ # @param default_value [Object, nil]
42
+ # @param required_type [Hackle::ValueType]
43
+ # @return [Hackle::RemoteConfigDecision]
44
+ def decision(key, default_value, required_type)
45
+ hackle_user = @user_resolver.resolve_or_nil(@user)
46
+ return RemoteConfigDecision.new(default_value, DecisionReason::INVALID_INPUT) if hackle_user.nil?
47
+ return RemoteConfigDecision.new(default_value, DecisionReason::INVALID_INPUT) if key.nil?
48
+
49
+ @core.remote_config(key, hackle_user, required_type, default_value)
50
+ rescue => e
51
+ Log.get.error { "Unexpected error while deciding remote config parameter[#{key}]. Returning default value: #{e.inspect}" }
52
+ RemoteConfigDecision.new(default_value, DecisionReason::EXCEPTION)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hackle/internal/identifiers/identifier_builder'
4
+ require 'hackle/internal/properties/properties_builder'
5
+
6
+ module Hackle
7
+ class User
8
+ # @return [String, nil]
9
+ attr_reader :id
10
+
11
+ # @return [String, nil]
12
+ attr_reader :user_id
13
+
14
+ # @return [String, nil]
15
+ attr_reader :device_id
16
+
17
+ # @return [Hash{String => String}]
18
+ attr_reader :identifiers
19
+
20
+ # @return [Hash{String => Object}]
21
+ attr_reader :properties
22
+
23
+ # @param id [String, nil]
24
+ # @param user_id [String, nil]
25
+ # @param device_id [String, nil]
26
+ # @param identifiers [Hash{String => String}]
27
+ # @param properties [Hash{String => Object}]
28
+ def initialize(id:, user_id:, device_id:, identifiers:, properties:)
29
+ @id = id
30
+ @user_id = user_id
31
+ @device_id = device_id
32
+ @identifiers = identifiers
33
+ @properties = properties
34
+ end
35
+
36
+ def ==(other)
37
+ return false unless other.is_a?(User)
38
+
39
+ id == other.id &&
40
+ user_id == other.user_id &&
41
+ device_id == other.device_id &&
42
+ identifiers == other.identifiers &&
43
+ properties == other.properties
44
+ end
45
+
46
+ def to_s
47
+ "Hackle::User(id: #{id}, user_id: #{user_id}, device_id: #{device_id}, identifiers: #{identifiers}, properties: #{properties})"
48
+ end
49
+
50
+ # @return [User::Builder]
51
+ def self.builder
52
+ Builder.new
53
+ end
54
+
55
+ class Builder
56
+ def initialize
57
+ @identifiers = IdentifiersBuilder.new
58
+ @properties = PropertiesBuilder.new
59
+ end
60
+
61
+ # @param id [String, Numeric]
62
+ # @return [User::Builder]
63
+ def id(id)
64
+ @id = IdentifiersBuilder.sanitize_value_or_nil(id)
65
+ self
66
+ end
67
+
68
+ # @param user_id [String, Numeric]
69
+ # @return [User::Builder]
70
+ def user_id(user_id)
71
+ @user_id = IdentifiersBuilder.sanitize_value_or_nil(user_id)
72
+ self
73
+ end
74
+
75
+ # @param device_id [String, Numeric]
76
+ # @return [User::Builder]
77
+ def device_id(device_id)
78
+ @device_id = IdentifiersBuilder.sanitize_value_or_nil(device_id)
79
+ self
80
+ end
81
+
82
+ # @param identifier_type [String]
83
+ # @param identifier_value [String, nil]
84
+ # @return [User::Builder]
85
+ def identifier(identifier_type, identifier_value)
86
+ @identifiers.add(identifier_type, identifier_value)
87
+ self
88
+ end
89
+
90
+ # @param identifiers [Hash{String => String}]
91
+ # @return [User::Builder]
92
+ def identifiers(identifiers)
93
+ @identifiers.add_all(identifiers)
94
+ self
95
+ end
96
+
97
+ # @param key [String]
98
+ # @param value [Object, nil]
99
+ # @return [User::Builder]
100
+ def property(key, value)
101
+ @properties.add(key, value)
102
+ self
103
+ end
104
+
105
+ # @param properties [Hash{String => Object}]
106
+ # @return [User::Builder]
107
+ def properties(properties)
108
+ @properties.add_all(properties)
109
+ self
110
+ end
111
+
112
+ # @return [User]
113
+ def build
114
+ User.new(
115
+ id: @id,
116
+ user_id: @user_id,
117
+ device_id: @device_id,
118
+ identifiers: @identifiers.build,
119
+ properties: @properties.build
120
+ )
121
+ end
122
+ end
123
+ end
124
+ end
@@ -1,15 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hackle
4
- VERSION = '1.0.0'
5
- SDK_NAME = 'ruby-sdk'
6
-
7
- class SdkInfo
8
- attr_reader :key, :name, :version
9
- def initialize(key:)
10
- @key = key
11
- @name = SDK_NAME
12
- @version = VERSION
13
- end
14
- end
4
+ VERSION = '2.0.0'
15
5
  end
data/lib/hackle.rb CHANGED
@@ -2,75 +2,10 @@
2
2
 
3
3
  require 'hackle/client'
4
4
  require 'hackle/config'
5
- require 'hackle/version'
5
+ require 'hackle/decision'
6
+ require 'hackle/user'
7
+ require 'hackle/event'
8
+ require 'hackle/remote_config'
6
9
 
7
10
  module Hackle
8
-
9
- #
10
- # Instantiates a Hackle client.
11
- #
12
- # @see Client#initialize
13
- #
14
- # @param sdk_key [String] The SDK key of your Hackle environment
15
- # @param options Optional parameters of configuration options
16
- #
17
- # @return [Client] The Hackle client instance.
18
- #
19
- def self.client(sdk_key:, **options)
20
- config = Config.new(options)
21
- sdk_info = SdkInfo.new(key: sdk_key)
22
-
23
- http_workspace_fetcher = HttpWorkspaceFetcher.new(config: config, sdk_info: sdk_info)
24
- polling_workspace_fetcher = PollingWorkspaceFetcher.new(config: config, http_fetcher: http_workspace_fetcher)
25
-
26
- event_dispatcher = EventDispatcher.new(config: config, sdk_info: sdk_info)
27
- event_processor = EventProcessor.new(config: config, event_dispatcher: event_dispatcher)
28
-
29
- polling_workspace_fetcher.start!
30
- event_processor.start!
31
-
32
- Client.new(
33
- config: config,
34
- workspace_fetcher: polling_workspace_fetcher,
35
- event_processor: event_processor,
36
- decider: Decider.new
37
- )
38
- end
39
-
40
- #
41
- # Instantiate a user to be used for the hackle sdk.
42
- #
43
- # The only required parameter is `id`, which must uniquely identify each user.
44
- #
45
- # @example
46
- # Hackle.user(id: 'ae2182e0')
47
- # Hackle.user(id: 'ae2182e0', app_version: '1.0.1', paying_customer: false)
48
- #
49
- # @param id [String] The identifier of the user. (e.g. device_id, account_id etc.)
50
- # @param properties Additional properties of the user. (e.g. app_version, membership_grade, etc.)
51
- #
52
- # @return [User] The configured user object.
53
- #
54
- def self.user(id:, **properties)
55
- User.new(id: id, properties: properties)
56
- end
57
-
58
- #
59
- # Instantiate an event to be used for the hackle sdk.
60
- #
61
- # The only required parameter is `key`, which must uniquely identify each event.
62
- #
63
- # @example
64
- # Hackle.event(key: 'purchase')
65
- # Hackle.event(key: 'purchase', value: 42000.0, app_version: '1.0.1', payment_method: 'CARD' )
66
- #
67
- # @param key [String] The unique key of the events.
68
- # @param value [Float] Optional numeric value of the events (e.g. purchase_amount, quantity, etc.)
69
- # @param properties Additional properties of the events (e.g. app_version, os_type, etc.)
70
- #
71
- # @return [Event] The configured event object.
72
- #
73
- def self.event(key:, value: nil, **properties)
74
- Event.new(key: key, value: value, properties: properties)
75
- end
76
11
  end