hackle-ruby-sdk 0.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hackle/client.rb +191 -79
  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/internal/evaluation/bucketer/bucketer.rb +46 -0
  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/internal/model/event_type.rb +19 -0
  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 -32
  71. metadata +123 -51
  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/bucketer.rb +0 -30
  80. data/lib/hackle/decision/decider.rb +0 -54
  81. data/lib/hackle/events/event.rb +0 -33
  82. data/lib/hackle/events/event_dispatcher.rb +0 -89
  83. data/lib/hackle/events/event_processor.rb +0 -115
  84. data/lib/hackle/http/http.rb +0 -37
  85. data/lib/hackle/models/bucket.rb +0 -15
  86. data/lib/hackle/models/event_type.rb +0 -14
  87. data/lib/hackle/models/experiment.rb +0 -36
  88. data/lib/hackle/models/slot.rb +0 -15
  89. data/lib/hackle/models/variation.rb +0 -11
  90. data/lib/hackle/workspaces/http_workspace_fetcher.rb +0 -24
  91. data/lib/hackle/workspaces/polling_workspace_fetcher.rb +0 -44
  92. data/lib/hackle/workspaces/workspace.rb +0 -87
@@ -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 = '0.1.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,38 +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 An optional client configuration
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
11
  end