launchdarkly-server-sdk 6.3.0 → 8.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -4
  3. data/lib/ldclient-rb/config.rb +112 -62
  4. data/lib/ldclient-rb/context.rb +444 -0
  5. data/lib/ldclient-rb/evaluation_detail.rb +26 -22
  6. data/lib/ldclient-rb/events.rb +256 -146
  7. data/lib/ldclient-rb/flags_state.rb +26 -15
  8. data/lib/ldclient-rb/impl/big_segments.rb +18 -18
  9. data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
  10. data/lib/ldclient-rb/impl/context.rb +96 -0
  11. data/lib/ldclient-rb/impl/context_filter.rb +145 -0
  12. data/lib/ldclient-rb/impl/data_source.rb +188 -0
  13. data/lib/ldclient-rb/impl/data_store.rb +59 -0
  14. data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
  15. data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
  16. data/lib/ldclient-rb/impl/evaluator.rb +386 -142
  17. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
  18. data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
  19. data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
  20. data/lib/ldclient-rb/impl/event_sender.rb +7 -6
  21. data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
  22. data/lib/ldclient-rb/impl/event_types.rb +136 -0
  23. data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
  24. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +19 -7
  25. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +38 -30
  26. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +24 -11
  27. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +109 -12
  28. data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
  29. data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
  30. data/lib/ldclient-rb/impl/model/clause.rb +45 -0
  31. data/lib/ldclient-rb/impl/model/feature_flag.rb +255 -0
  32. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
  33. data/lib/ldclient-rb/impl/model/segment.rb +132 -0
  34. data/lib/ldclient-rb/impl/model/serialization.rb +54 -44
  35. data/lib/ldclient-rb/impl/repeating_task.rb +3 -4
  36. data/lib/ldclient-rb/impl/sampler.rb +25 -0
  37. data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
  38. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
  39. data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
  40. data/lib/ldclient-rb/impl/util.rb +59 -1
  41. data/lib/ldclient-rb/in_memory_store.rb +9 -2
  42. data/lib/ldclient-rb/integrations/consul.rb +2 -2
  43. data/lib/ldclient-rb/integrations/dynamodb.rb +2 -2
  44. data/lib/ldclient-rb/integrations/file_data.rb +4 -4
  45. data/lib/ldclient-rb/integrations/redis.rb +5 -5
  46. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +287 -62
  47. data/lib/ldclient-rb/integrations/test_data.rb +18 -14
  48. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +20 -9
  49. data/lib/ldclient-rb/interfaces.rb +600 -14
  50. data/lib/ldclient-rb/ldclient.rb +314 -134
  51. data/lib/ldclient-rb/memoized_value.rb +1 -1
  52. data/lib/ldclient-rb/migrations.rb +230 -0
  53. data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
  54. data/lib/ldclient-rb/polling.rb +52 -6
  55. data/lib/ldclient-rb/reference.rb +274 -0
  56. data/lib/ldclient-rb/requestor.rb +9 -11
  57. data/lib/ldclient-rb/stream.rb +96 -34
  58. data/lib/ldclient-rb/util.rb +97 -14
  59. data/lib/ldclient-rb/version.rb +1 -1
  60. data/lib/ldclient-rb.rb +3 -4
  61. metadata +65 -23
  62. data/lib/ldclient-rb/event_summarizer.rb +0 -55
  63. data/lib/ldclient-rb/file_data_source.rb +0 -23
  64. data/lib/ldclient-rb/impl/event_factory.rb +0 -126
  65. data/lib/ldclient-rb/newrelic.rb +0 -17
  66. data/lib/ldclient-rb/redis_store.rb +0 -88
  67. data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -43,9 +43,50 @@ module LaunchDarkly
43
43
  self
44
44
  end
45
45
 
46
+ #
47
+ # Set the migration related settings for this feature flag.
48
+ #
49
+ # The settings hash should be built using the {FlagMigrationSettingsBuilder}.
50
+ #
51
+ # @param settings [Hash]
52
+ # @return [FlagBuilder] the builder
53
+ #
54
+ def migration_settings(settings)
55
+ @migration_settings = settings
56
+ self
57
+ end
58
+
59
+ #
60
+ # Set the sampling ratio for this flag. This ratio is used to control the emission rate of feature, debug, and
61
+ # migration op events.
62
+ #
63
+ # General usage should not require interacting with this method.
64
+ #
65
+ # @param ratio [Integer]
66
+ # @return [FlagBuilder]
67
+ #
68
+ def sampling_ratio(ratio)
69
+ @sampling_ratio = ratio
70
+ self
71
+ end
72
+
73
+ #
74
+ # Set the option to exclude this flag from summary events. This is used to control the size of the summary event
75
+ # in the event certain flag payloads are large.
76
+ #
77
+ # General usage should not require interacting with this method.
78
+ #
79
+ # @param exclude [Boolean]
80
+ # @return [FlagBuilder]
81
+ #
82
+ def exclude_from_summaries(exclude)
83
+ @exclude_from_summaries = exclude
84
+ self
85
+ end
86
+
46
87
  #
47
88
  # Specifies the fallthrough variation. The fallthrough is the value
48
- # that is returned if targeting is on and the user was not matched by a more specific
89
+ # that is returned if targeting is on and the context was not matched by a more specific
49
90
  # target or rule.
50
91
  #
51
92
  # If the flag was previously configured with other variations and the variation specified is a boolean,
@@ -56,7 +97,7 @@ module LaunchDarkly
56
97
  # @return [FlagBuilder] the builder
57
98
  #
58
99
  def fallthrough_variation(variation)
59
- if LaunchDarkly::Impl::Util.is_bool variation then
100
+ if LaunchDarkly::Impl::Util.bool? variation
60
101
  boolean_flag.fallthrough_variation(variation_for_boolean(variation))
61
102
  else
62
103
  @fallthrough_variation = variation
@@ -76,7 +117,7 @@ module LaunchDarkly
76
117
  # @return [FlagBuilder] the builder
77
118
  #
78
119
  def off_variation(variation)
79
- if LaunchDarkly::Impl::Util.is_bool variation then
120
+ if LaunchDarkly::Impl::Util.bool? variation
80
121
  boolean_flag.off_variation(variation_for_boolean(variation))
81
122
  else
82
123
  @off_variation = variation
@@ -108,7 +149,7 @@ module LaunchDarkly
108
149
  end
109
150
 
110
151
  #
111
- # Sets the flag to always return the specified variation for all users.
152
+ # Sets the flag to always return the specified variation for all contexts.
112
153
  #
113
154
  # The variation is specified, Targeting is switched on, and any existing targets or rules are removed.
114
155
  # The fallthrough variation is set to the specified value. The off variation is left unchanged.
@@ -120,31 +161,31 @@ module LaunchDarkly
120
161
  # 0 for the first, 1 for the second, etc.
121
162
  # @return [FlagBuilder] the builder
122
163
  #
123
- def variation_for_all_users(variation)
124
- if LaunchDarkly::Impl::Util.is_bool variation then
125
- boolean_flag.variation_for_all_users(variation_for_boolean(variation))
164
+ def variation_for_all(variation)
165
+ if LaunchDarkly::Impl::Util.bool? variation
166
+ boolean_flag.variation_for_all(variation_for_boolean(variation))
126
167
  else
127
- on(true).clear_rules.clear_user_targets.fallthrough_variation(variation)
168
+ on(true).clear_rules.clear_targets.fallthrough_variation(variation)
128
169
  end
129
170
  end
130
171
 
131
172
  #
132
- # Sets the flag to always return the specified variation value for all users.
173
+ # Sets the flag to always return the specified variation value for all context.
133
174
  #
134
175
  # The value may be of any valid JSON type. This method changes the
135
176
  # flag to have only a single variation, which is this value, and to return the same
136
177
  # variation regardless of whether targeting is on or off. Any existing targets or rules
137
178
  # are removed.
138
179
  #
139
- # @param value [Object] the desired value to be returned for all users
180
+ # @param value [Object] the desired value to be returned for all contexts
140
181
  # @return [FlagBuilder] the builder
141
182
  #
142
- def value_for_all_users(value)
143
- variations(value).variation_for_all_users(0)
183
+ def value_for_all(value)
184
+ variations(value).variation_for_all(0)
144
185
  end
145
186
 
146
187
  #
147
- # Sets the flag to return the specified variation for a specific user key when targeting
188
+ # Sets the flag to return the specified variation for a specific context key when targeting
148
189
  # is on.
149
190
  #
150
191
  # This has no effect when targeting is turned off for the flag.
@@ -152,36 +193,87 @@ module LaunchDarkly
152
193
  # If the flag was previously configured with other variations and the variation specified is a boolean,
153
194
  # this also changes it to a boolean flag.
154
195
  #
155
- # @param user_key [String] a user key
196
+ # @param context_kind [String] a context kind
197
+ # @param context_key [String] a context key
156
198
  # @param variation [Boolean, Integer] true or false or the desired variation index to return:
157
199
  # 0 for the first, 1 for the second, etc.
158
200
  # @return [FlagBuilder] the builder
159
201
  #
160
- def variation_for_user(user_key, variation)
161
- if LaunchDarkly::Impl::Util.is_bool variation then
162
- boolean_flag.variation_for_user(user_key, variation_for_boolean(variation))
163
- else
164
- if @targets.nil? then
165
- @targets = Hash.new
166
- end
167
- @variations.count.times do | i |
168
- if i == variation then
169
- if @targets[i].nil? then
170
- @targets[i] = [user_key]
171
- else
172
- @targets[i].push(user_key)
173
- end
174
- elsif not @targets[i].nil? then
175
- @targets[i].delete(user_key)
202
+ def variation_for_key(context_kind, context_key, variation)
203
+ if LaunchDarkly::Impl::Util.bool? variation
204
+ return boolean_flag.variation_for_key(context_kind, context_key, variation_for_boolean(variation))
205
+ end
206
+
207
+ if @targets.nil?
208
+ @targets = Hash.new
209
+ end
210
+
211
+ targets = @targets[context_kind] || []
212
+ @variations.count.times do | i |
213
+ if i == variation
214
+ if targets[i].nil?
215
+ targets[i] = [context_key]
216
+ else
217
+ targets[i].push(context_key)
176
218
  end
219
+ elsif not targets[i].nil?
220
+ targets[i].delete(context_key)
177
221
  end
178
- self
179
222
  end
223
+
224
+ @targets[context_kind] = targets
225
+
226
+ self
227
+ end
228
+
229
+ #
230
+ # Sets the flag to return the specified variation for a specific user key when targeting
231
+ # is on.
232
+ #
233
+ # This is a shortcut for calling {variation_for_key} with
234
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
235
+ #
236
+ # This has no effect when targeting is turned off for the flag.
237
+ #
238
+ # If the flag was previously configured with other variations and the variation specified is a boolean,
239
+ # this also changes it to a boolean flag.
240
+ #
241
+ # @param user_key [String] a user key
242
+ # @param variation [Boolean, Integer] true or false or the desired variation index to return:
243
+ # 0 for the first, 1 for the second, etc.
244
+ # @return [FlagBuilder] the builder
245
+ #
246
+ def variation_for_user(user_key, variation)
247
+ variation_for_key(LaunchDarkly::LDContext::KIND_DEFAULT, user_key, variation)
248
+ end
249
+
250
+ #
251
+ # Starts defining a flag rule, using the "is one of" operator.
252
+ #
253
+ # @example create a rule that returns `true` if the name is "Patsy" or "Edina" and the context kind is "user"
254
+ # testData.flag("flag")
255
+ # .if_match_context("user", :name, 'Patsy', 'Edina')
256
+ # .then_return(true);
257
+ #
258
+ # @param context_kind [String] a context kind
259
+ # @param attribute [Symbol] the context attribute to match against
260
+ # @param values [Array<Object>] values to compare to
261
+ # @return [FlagRuleBuilder] a flag rule builder
262
+ #
263
+ # @see FlagRuleBuilder#then_return
264
+ # @see FlagRuleBuilder#and_match
265
+ # @see FlagRuleBuilder#and_not_match
266
+ #
267
+ def if_match_context(context_kind, attribute, *values)
268
+ FlagRuleBuilder.new(self).and_match_context(context_kind, attribute, *values)
180
269
  end
181
270
 
182
271
  #
183
272
  # Starts defining a flag rule, using the "is one of" operator.
184
273
  #
274
+ # This is a shortcut for calling {if_match_context} with
275
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
276
+ #
185
277
  # @example create a rule that returns `true` if the name is "Patsy" or "Edina"
186
278
  # testData.flag("flag")
187
279
  # .if_match(:name, 'Patsy', 'Edina')
@@ -196,7 +288,7 @@ module LaunchDarkly
196
288
  # @see FlagRuleBuilder#and_not_match
197
289
  #
198
290
  def if_match(attribute, *values)
199
- FlagRuleBuilder.new(self).and_match(attribute, *values)
291
+ if_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
200
292
  end
201
293
 
202
294
  #
@@ -204,6 +296,30 @@ module LaunchDarkly
204
296
  #
205
297
  # @example create a rule that returns `true` if the name is neither "Saffron" nor "Bubble"
206
298
  # testData.flag("flag")
299
+ # .if_not_match_context("user", :name, 'Saffron', 'Bubble')
300
+ # .then_return(true)
301
+ #
302
+ # @param context_kind [String] a context kind
303
+ # @param attribute [Symbol] the context attribute to match against
304
+ # @param values [Array<Object>] values to compare to
305
+ # @return [FlagRuleBuilder] a flag rule builder
306
+ #
307
+ # @see FlagRuleBuilder#then_return
308
+ # @see FlagRuleBuilder#and_match
309
+ # @see FlagRuleBuilder#and_not_match
310
+ #
311
+ def if_not_match_context(context_kind, attribute, *values)
312
+ FlagRuleBuilder.new(self).and_not_match_context(context_kind, attribute, *values)
313
+ end
314
+
315
+ #
316
+ # Starts defining a flag rule, using the "is not one of" operator.
317
+ #
318
+ # This is a shortcut for calling {if_not_match_context} with
319
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
320
+ #
321
+ # @example create a rule that returns `true` if the name is neither "Saffron" nor "Bubble"
322
+ # testData.flag("flag")
207
323
  # .if_not_match(:name, 'Saffron', 'Bubble')
208
324
  # .then_return(true)
209
325
  #
@@ -216,16 +332,16 @@ module LaunchDarkly
216
332
  # @see FlagRuleBuilder#and_not_match
217
333
  #
218
334
  def if_not_match(attribute, *values)
219
- FlagRuleBuilder.new(self).and_not_match(attribute, *values)
335
+ if_not_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
220
336
  end
221
337
 
222
338
  #
223
- # Removes any existing user targets from the flag.
224
- # This undoes the effect of methods like {#variation_for_user}
339
+ # Removes any existing targets from the flag.
340
+ # This undoes the effect of methods like {#variation_for_key}
225
341
  #
226
342
  # @return [FlagBuilder] the same builder
227
343
  #
228
- def clear_user_targets
344
+ def clear_targets
229
345
  @targets = nil
230
346
  self
231
347
  end
@@ -243,7 +359,7 @@ module LaunchDarkly
243
359
 
244
360
  # @private
245
361
  def add_rule(rule)
246
- if @rules.nil? then
362
+ if @rules.nil?
247
363
  @rules = Array.new
248
364
  end
249
365
  @rules.push(rule)
@@ -261,7 +377,7 @@ module LaunchDarkly
261
377
  # @return [FlagBuilder] the builder
262
378
  #
263
379
  def boolean_flag
264
- if is_boolean_flag then
380
+ if boolean_flag?
265
381
  self
266
382
  else
267
383
  variations(true, false)
@@ -278,33 +394,90 @@ module LaunchDarkly
278
394
  variations: @variations,
279
395
  }
280
396
 
281
- unless @off_variation.nil? then
397
+ unless @off_variation.nil?
282
398
  res[:offVariation] = @off_variation
283
399
  end
284
400
 
285
- unless @fallthrough_variation.nil? then
401
+ unless @fallthrough_variation.nil?
286
402
  res[:fallthrough] = { variation: @fallthrough_variation }
287
403
  end
288
404
 
289
- unless @targets.nil? then
290
- res[:targets] = @targets.collect do | variation, values |
291
- { variation: variation, values: values }
405
+ unless @migration_settings.nil?
406
+ res[:migration] = @migration_settings
407
+ end
408
+
409
+ unless @sampling_ratio.nil? || @sampling_ratio == 1
410
+ res[:samplingRatio] = @sampling_ratio
411
+ end
412
+
413
+ unless @exclude_from_summaries.nil? || !@exclude_from_summaries
414
+ res[:excludeFromSummaries] = @exclude_from_summaries
415
+ end
416
+
417
+ unless @targets.nil?
418
+ targets = []
419
+ context_targets = []
420
+
421
+ @targets.each do |kind, targets_for_kind|
422
+ targets_for_kind.each_with_index do |values, variation|
423
+ next if values.nil?
424
+ if kind == LaunchDarkly::LDContext::KIND_DEFAULT
425
+ targets << { variation: variation, values: values }
426
+ context_targets << { contextKind: LaunchDarkly::LDContext::KIND_DEFAULT, variation: variation, values: [] }
427
+ else
428
+ context_targets << { contextKind: kind, variation: variation, values: values }
429
+ end
430
+ end
292
431
  end
432
+
433
+ res[:targets] = targets
434
+ res[:contextTargets] = context_targets
293
435
  end
294
436
 
295
- unless @rules.nil? then
296
- res[:rules] = @rules.each_with_index.collect { | rule, i | rule.build(i) }
437
+ unless @rules.nil?
438
+ res[:rules] = @rules.each_with_index.map { | rule, i | rule.build(i) }
297
439
  end
298
440
 
299
441
  res
300
442
  end
301
443
 
444
+ #
445
+ # A builder for feature flag migration settings to be used with {FlagBuilder}.
446
+ #
447
+ # In the LaunchDarkly model, a flag can be a standard feature flag, or it can be a migration-related flag, in
448
+ # which case it has migration-specified related settings. These settings control things like the rate at which
449
+ # reads are tested for consistency between origins.
450
+ #
451
+ class FlagMigrationSettingsBuilder
452
+ def initialize()
453
+ @check_ratio = nil
454
+ end
455
+
456
+ #
457
+ # @param ratio [Integer]
458
+ # @return [FlagMigrationSettingsBuilder]
459
+ #
460
+ def check_ratio(ratio)
461
+ return unless ratio.is_a? Integer
462
+ @check_ratio = ratio
463
+ self
464
+ end
465
+
466
+ def build
467
+ return nil if @check_ratio.nil? || @check_ratio == 1
468
+
469
+ {
470
+ "checkRatio": @check_ratio,
471
+ }
472
+ end
473
+ end
474
+
302
475
  #
303
476
  # A builder for feature flag rules to be used with {FlagBuilder}.
304
477
  #
305
478
  # In the LaunchDarkly model, a flag can have any number of rules, and a rule can have any number of
306
- # clauses. A clause is an individual test such as "name is 'X'". A rule matches a user if all of the
307
- # rule's clauses match the user.
479
+ # clauses. A clause is an individual test such as "name is 'X'". A rule matches a context if all of the
480
+ # rule's clauses match the context.
308
481
  #
309
482
  # To start defining a rule, use one of the flag builder's matching methods such as
310
483
  # {FlagBuilder#if_match}. This defines the first clause for the rule.
@@ -314,7 +487,7 @@ module LaunchDarkly
314
487
  #
315
488
  class FlagRuleBuilder
316
489
  # @private
317
- FlagRuleClause = Struct.new(:attribute, :op, :values, :negate, keyword_init: true)
490
+ FlagRuleClause = Struct.new(:contextKind, :attribute, :op, :values, :negate, keyword_init: true)
318
491
 
319
492
  # @private
320
493
  def initialize(flag_builder)
@@ -331,6 +504,34 @@ module LaunchDarkly
331
504
  #
332
505
  # Adds another clause, using the "is one of" operator.
333
506
  #
507
+ # @example create a rule that returns `true` if the name is "Patsy", the country is "gb", and the context kind is "user"
508
+ # testData.flag("flag")
509
+ # .if_match_context("user", :name, 'Patsy')
510
+ # .and_match_context("user", :country, 'gb')
511
+ # .then_return(true)
512
+ #
513
+ # @param context_kind [String] a context kind
514
+ # @param attribute [Symbol] the context attribute to match against
515
+ # @param values [Array<Object>] values to compare to
516
+ # @return [FlagRuleBuilder] the rule builder
517
+ #
518
+ def and_match_context(context_kind, attribute, *values)
519
+ @clauses.push(FlagRuleClause.new(
520
+ contextKind: context_kind,
521
+ attribute: attribute,
522
+ op: 'in',
523
+ values: values,
524
+ negate: false
525
+ ))
526
+ self
527
+ end
528
+
529
+ #
530
+ # Adds another clause, using the "is one of" operator.
531
+ #
532
+ # This is a shortcut for calling {and_match_context} with
533
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
534
+ #
334
535
  # @example create a rule that returns `true` if the name is "Patsy" and the country is "gb"
335
536
  # testData.flag("flag")
336
537
  # .if_match(:name, 'Patsy')
@@ -342,11 +543,30 @@ module LaunchDarkly
342
543
  # @return [FlagRuleBuilder] the rule builder
343
544
  #
344
545
  def and_match(attribute, *values)
546
+ and_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
547
+ end
548
+
549
+ #
550
+ # Adds another clause, using the "is not one of" operator.
551
+ #
552
+ # @example create a rule that returns `true` if the name is "Patsy" and the country is not "gb"
553
+ # testData.flag("flag")
554
+ # .if_match_context("user", :name, 'Patsy')
555
+ # .and_not_match_context("user", :country, 'gb')
556
+ # .then_return(true)
557
+ #
558
+ # @param context_kind [String] a context kind
559
+ # @param attribute [Symbol] the context attribute to match against
560
+ # @param values [Array<Object>] values to compare to
561
+ # @return [FlagRuleBuilder] the rule builder
562
+ #
563
+ def and_not_match_context(context_kind, attribute, *values)
345
564
  @clauses.push(FlagRuleClause.new(
565
+ contextKind: context_kind,
346
566
  attribute: attribute,
347
567
  op: 'in',
348
568
  values: values,
349
- negate: false
569
+ negate: true
350
570
  ))
351
571
  self
352
572
  end
@@ -354,6 +574,9 @@ module LaunchDarkly
354
574
  #
355
575
  # Adds another clause, using the "is not one of" operator.
356
576
  #
577
+ # This is a shortcut for calling {and_not_match} with
578
+ # `LaunchDarkly::LDContext::KIND_DEFAULT` as the context kind.
579
+ #
357
580
  # @example create a rule that returns `true` if the name is "Patsy" and the country is not "gb"
358
581
  # testData.flag("flag")
359
582
  # .if_match(:name, 'Patsy')
@@ -365,13 +588,7 @@ module LaunchDarkly
365
588
  # @return [FlagRuleBuilder] the rule builder
366
589
  #
367
590
  def and_not_match(attribute, *values)
368
- @clauses.push(FlagRuleClause.new(
369
- attribute: attribute,
370
- op: 'in',
371
- values: values,
372
- negate: true
373
- ))
374
- self
591
+ and_not_match_context(LaunchDarkly::LDContext::KIND_DEFAULT, attribute, *values)
375
592
  end
376
593
 
377
594
  #
@@ -386,7 +603,7 @@ module LaunchDarkly
386
603
  # @return [FlagBuilder] the flag builder with this rule added
387
604
  #
388
605
  def then_return(variation)
389
- if LaunchDarkly::Impl::Util.is_bool variation then
606
+ if LaunchDarkly::Impl::Util.bool? variation
390
607
  @variation = @flag_builder.variation_for_boolean(variation)
391
608
  @flag_builder.boolean_flag.add_rule(self)
392
609
  else
@@ -400,7 +617,7 @@ module LaunchDarkly
400
617
  {
401
618
  id: 'rule' + ri.to_s,
402
619
  variation: @variation,
403
- clauses: @clauses.collect(&:to_h)
620
+ clauses: @clauses.map(&:to_h),
404
621
  }
405
622
  end
406
623
  end
@@ -415,15 +632,23 @@ module LaunchDarkly
415
632
  TRUE_VARIATION_INDEX = 0
416
633
  FALSE_VARIATION_INDEX = 1
417
634
 
418
- def is_boolean_flag
635
+ def boolean_flag?
419
636
  @variations.size == 2 &&
420
- @variations[TRUE_VARIATION_INDEX] == true &&
421
- @variations[FALSE_VARIATION_INDEX] == false
637
+ @variations[TRUE_VARIATION_INDEX] == true &&
638
+ @variations[FALSE_VARIATION_INDEX] == false
422
639
  end
423
640
 
424
641
  def deep_copy_hash(from)
425
642
  to = Hash.new
426
- from.each { |k, v| to[k] = v.clone }
643
+ from.each do |k, v|
644
+ if v.is_a?(Hash)
645
+ to[k] = deep_copy_hash(v)
646
+ elsif v.is_a?(Array)
647
+ to[k] = deep_copy_array(v)
648
+ else
649
+ to[k] = v.clone
650
+ end
651
+ end
427
652
  to
428
653
  end
429
654
 
@@ -1,4 +1,6 @@
1
1
  require 'ldclient-rb/impl/integrations/test_data/test_data_source'
2
+ require 'ldclient-rb/impl/model/feature_flag'
3
+ require 'ldclient-rb/impl/model/segment'
2
4
  require 'ldclient-rb/integrations/test_data/flag_builder'
3
5
 
4
6
  require 'concurrent/atomics'
@@ -14,12 +16,12 @@ module LaunchDarkly
14
16
  #
15
17
  # @example
16
18
  # td = LaunchDarkly::Integrations::TestData.data_source
17
- # td.update(td.flag("flag-key-1").variation_for_all_users(true))
19
+ # td.update(td.flag("flag-key-1").variation_for_all(true))
18
20
  # config = LaunchDarkly::Config.new(data_source: td)
19
21
  # client = LaunchDarkly::LDClient.new('sdkKey', config)
20
22
  # # flags can be updated at any time:
21
23
  # td.update(td.flag("flag-key-2")
22
- # .variation_for_user("some-user-key", true)
24
+ # .variation_for_key("user", some-user-key", true)
23
25
  # .fallthrough_variation(false))
24
26
  #
25
27
  # The above example uses a simple boolean flag, but more complex configurations are possible using
@@ -77,7 +79,7 @@ module LaunchDarkly
77
79
  # starts with the same configuration that was last provided for this flag.
78
80
  #
79
81
  # Otherwise, it starts with a new default configuration in which the flag has `true` and
80
- # `false` variations, is `true` for all users when targeting is turned on and
82
+ # `false` variations, is `true` for all contexts when targeting is turned on and
81
83
  # `false` otherwise, and currently has targeting turned on. You can change any of those
82
84
  # properties, and provide more complex behavior, using the {FlagBuilder} methods.
83
85
  #
@@ -88,7 +90,7 @@ module LaunchDarkly
88
90
  #
89
91
  def flag(key)
90
92
  existing_builder = @lock.with_read_lock { @flag_builders[key] }
91
- if existing_builder.nil? then
93
+ if existing_builder.nil?
92
94
  FlagBuilder.new(key).boolean_flag
93
95
  else
94
96
  existing_builder.clone
@@ -116,10 +118,10 @@ module LaunchDarkly
116
118
  @flag_builders[flag_builder.key] = flag_builder
117
119
  version = 0
118
120
  flag_key = flag_builder.key.to_sym
119
- if @current_flags[flag_key] then
121
+ if @current_flags[flag_key]
120
122
  version = @current_flags[flag_key][:version]
121
123
  end
122
- new_flag = flag_builder.build(version+1)
124
+ new_flag = Impl::Model.deserialize(FEATURES, flag_builder.build(version+1))
123
125
  @current_flags[flag_key] = new_flag
124
126
  end
125
127
  update_item(FEATURES, new_flag)
@@ -149,15 +151,15 @@ module LaunchDarkly
149
151
  end
150
152
 
151
153
  #
152
- # Copies a full user segment data model object into the test data.
154
+ # Copies a full segment data model object into the test data.
153
155
  #
154
156
  # It immediately propagates the change to any `LDClient` instance(s) that you have already
155
157
  # configured to use this `TestData`. If no `LDClient` has been started yet, it simply adds
156
158
  # this segment to the test data which will be provided to any LDClient that you subsequently
157
159
  # configure.
158
160
  #
159
- # This method is currently the only way to inject user segment data, since there is no builder
160
- # API for segments. It is mainly intended for the SDK's own tests of user segment functionality,
161
+ # This method is currently the only way to inject segment data, since there is no builder
162
+ # API for segments. It is mainly intended for the SDK's own tests of segment functionality,
161
163
  # since application tests that need to produce a desired evaluation state could do so more easily
162
164
  # by just setting flag values.
163
165
  #
@@ -169,12 +171,14 @@ module LaunchDarkly
169
171
  end
170
172
 
171
173
  private def use_preconfigured_item(kind, item, current)
172
- key = item[:key].to_sym
174
+ item = Impl::Model.deserialize(kind, item)
175
+ key = item.key.to_sym
173
176
  @lock.with_write_lock do
174
177
  old_item = current[key]
175
- if !old_item.nil? then
176
- item = item.clone
177
- item[:version] = old_item[:version] + 1
178
+ unless old_item.nil?
179
+ data = item.as_json
180
+ data[:version] = old_item.version + 1
181
+ item = Impl::Model.deserialize(kind, data)
178
182
  end
179
183
  current[key] = item
180
184
  end
@@ -195,7 +199,7 @@ module LaunchDarkly
195
199
  @lock.with_read_lock do
196
200
  {
197
201
  FEATURES => @current_flags.clone,
198
- SEGMENTS => @current_segments.clone
202
+ SEGMENTS => @current_segments.clone,
199
203
  }
200
204
  end
201
205
  end