launchdarkly-server-sdk 6.3.0 → 8.0.0

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