verdict 0.11.0 → 0.12.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e98209c988365064ac1570a47b2dc5c0b0663f673ec62cb8d2640bdf38c9f3d
4
- data.tar.gz: 1111714a3fbfffc8ee878c49db5dbaacc5e0c8d6c53f8425dfe69cfdcde96e38
3
+ metadata.gz: 7e99d3cb09e2dbfb3cafeb6f05171978721cd5a79fd4570f8b6bd6c6e4975e17
4
+ data.tar.gz: 4a885cb9d8a991046e3a22e428a35982bf7565b03aaa49fa0da0b9963e34bc56
5
5
  SHA512:
6
- metadata.gz: 71b77a462635384229bcce809954d15fac8e75fe3d307172eb1f31a5950e92e36f0a51c686c0cdb22d89fafcb0c5d6e10ba9a51b708580ac28e7ab480698fde7
7
- data.tar.gz: 48b4b96643a1235479061ceec0954700a7997105f37b03b51833dff7becd5f6db11582f177df81fcc79c56ed5a6b9b2ecd038a33c2e2233f74f69185cbe49a7a
6
+ metadata.gz: 89c2d20c1892c630942099ac0b0ea1d3db012a6aaa9031a7bbf8d834fad295adfaf4cc0194d09930608c4416bbeb8efba89852446e02b091c237d851d8266704
7
+ data.tar.gz: cdb0271d66903d1f39316c2665c68bf8deae733c105e3aecc8bb665adaef4981129bb0f7cce6baf001eb7f246af50e40d85115bf3105525a44b2111fb2c41fbc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## v0.12.0
2
+
3
+ * Allow options to be passed to `Experiment#cleanup` so they can be forwarded to storage.
4
+
5
+ * Changed `Experiment#cleanup` to accept an argument of type `Verdict::Experiment`.
6
+ Passing a `String`/`Symbol` argument is still supported, but will log a deprecation warning.
7
+
1
8
  ## v0.11.0
2
9
 
3
10
  * Automatic eager loading when inside a Rails app.
data/lib/verdict.rb CHANGED
@@ -58,3 +58,4 @@ require "verdict/event_logger"
58
58
 
59
59
  Verdict.default_logger ||= Logger.new("/dev/null")
60
60
  Verdict.directory = nil
61
+ Verdict.clear_repository_cache
@@ -2,7 +2,7 @@ class Verdict::Experiment
2
2
 
3
3
  include Verdict::Metadata
4
4
 
5
- attr_reader :handle, :qualifiers, :storage, :event_logger
5
+ attr_reader :handle, :qualifiers, :event_logger
6
6
 
7
7
  def self.define(handle, *args, &block)
8
8
  experiment = self.new(handle, *args, &block)
@@ -11,6 +11,7 @@ class Verdict::Experiment
11
11
  end
12
12
 
13
13
  def initialize(handle, options = {}, &block)
14
+ @started_at = nil
14
15
  @handle = handle.to_s
15
16
 
16
17
  options = default_options.merge(options)
@@ -88,7 +89,7 @@ class Verdict::Experiment
88
89
 
89
90
  def started_at
90
91
  @started_at ||= @storage.retrieve_start_timestamp(self)
91
- rescue Verdict::StorageError => e
92
+ rescue Verdict::StorageError
92
93
  nil
93
94
  end
94
95
 
@@ -162,8 +163,8 @@ class Verdict::Experiment
162
163
  assignment
163
164
  end
164
165
 
165
- def cleanup
166
- @storage.cleanup(handle.to_s)
166
+ def cleanup(options = {})
167
+ @storage.cleanup(self, options)
167
168
  end
168
169
 
169
170
  def remove_subject_assignment(subject)
@@ -193,15 +194,13 @@ class Verdict::Experiment
193
194
  end
194
195
 
195
196
  def as_json(options = {})
196
- data = {
197
+ {
197
198
  handle: handle,
198
199
  has_qualifier: has_qualifier?,
199
- groups: segmenter.groups.values.map { |g| g.as_json(options) },
200
+ groups: segmenter.groups.values.map { |group| group.as_json(options) },
200
201
  metadata: metadata,
201
202
  started_at: started_at.nil? ? nil : started_at.utc.strftime('%FT%TZ')
202
- }
203
-
204
- data.tap do |data|
203
+ }.tap do |data|
205
204
  data[:subject_type] = subject_type.to_s unless subject_type.nil?
206
205
  end
207
206
  end
@@ -45,11 +45,21 @@ module Verdict
45
45
  set(experiment.handle.to_s, 'started_at', timestamp.utc.strftime('%FT%TZ'))
46
46
  end
47
47
 
48
- def cleanup(_scope)
49
- raise NotImplementedError
48
+ # Deletes all assignments (and any other stored data) for the given experiment
49
+ def cleanup(experiment_or_scope, options = {})
50
+ if experiment_or_scope.is_a?(Symbol) || experiment_or_scope.is_a?(String)
51
+ Verdict.default_logger.warn(
52
+ "Passing a scope string/symbol to #{self.class}#cleanup is deprecated, " \
53
+ 'pass a Verdict::Experiment instance instead.'
54
+ )
55
+ clear(experiment_or_scope, options)
56
+ else
57
+ clear(experiment_or_scope.handle.to_s, options)
58
+ end
50
59
  end
51
60
 
52
61
  protected
62
+
53
63
  # Retrieves a key in a given scope from storage.
54
64
  # - The scope and key are both provided as string.
55
65
  # - Should return a string value if the key is found in the scope, nil otherwise.
@@ -58,7 +68,7 @@ module Verdict
58
68
  raise NotImplementedError
59
69
  end
60
70
 
61
- # Retrieves a key in a given scope from storage.
71
+ # Sets the value of a key in a given scope from storage.
62
72
  # - The scope, key, and value are all provided as string.
63
73
  # - Should return true if the item was successfully stored.
64
74
  # - Should raise Verdict::StorageError if anything goes wrong.
@@ -66,13 +76,21 @@ module Verdict
66
76
  raise NotImplementedError
67
77
  end
68
78
 
69
- # Retrieves a key in a given scope from storage.
79
+ # Removes a key in a given scope from storage.
70
80
  # - The scope and key are both provided as string.
71
81
  # - Should return true if the item was successfully removed from storage.
72
82
  # - Should raise Verdict::StorageError if anything goes wrong.
73
83
  def remove(scope, key)
74
84
  raise NotImplementedError
75
85
  end
86
+
87
+ # Removes all keys in a given scope from storage.
88
+ # - The scope is provided as string.
89
+ # - Should return true if all items were successfully removed from storage.
90
+ # - Should raise Verdict::StorageError if anything goes wrong.
91
+ def clear(scope, options)
92
+ raise NotImplementedError
93
+ end
76
94
  end
77
95
  end
78
96
  end
@@ -7,6 +7,8 @@ module Verdict
7
7
  @storage = {}
8
8
  end
9
9
 
10
+ protected
11
+
10
12
  def get(scope, key)
11
13
  @storage[scope] ||= {}
12
14
  @storage[scope][key]
@@ -1,14 +1,16 @@
1
1
  module Verdict
2
2
  module Storage
3
3
  class MockStorage < BaseStorage
4
- def set(scope, key, value)
5
- false
6
- end
4
+ protected
7
5
 
8
6
  def get(scope, key)
9
7
  nil
10
8
  end
11
9
 
10
+ def set(scope, key, value)
11
+ false
12
+ end
13
+
12
14
  def remove(scope, key)
13
15
  end
14
16
  end
@@ -10,6 +10,8 @@ module Verdict
10
10
  @key_prefix = options[:key_prefix] || 'experiments/'
11
11
  end
12
12
 
13
+ protected
14
+
13
15
  def get(scope, key)
14
16
  redis.hget(scope_key(scope), key)
15
17
  rescue ::Redis::BaseError => e
@@ -28,8 +30,8 @@ module Verdict
28
30
  raise Verdict::StorageError, "Redis error: #{e.message}"
29
31
  end
30
32
 
31
- def cleanup(scope)
32
- clear(scope)
33
+ def clear(scope, options)
34
+ scrub(scope)
33
35
  redis.del(scope_key(scope))
34
36
  rescue ::Redis::BaseError => e
35
37
  raise Verdict::StorageError, "Redis error: #{e.message}"
@@ -41,12 +43,12 @@ module Verdict
41
43
  "#{@key_prefix}#{scope}"
42
44
  end
43
45
 
44
- def clear(scope, cursor: 0)
46
+ def scrub(scope, cursor: 0)
45
47
  cursor, results = redis.hscan(scope_key(scope), cursor, count: PAGE_SIZE)
46
48
  results.map(&:first).each do |key|
47
49
  remove(scope, key)
48
50
  end
49
- clear(scope, cursor: cursor) unless cursor.to_i.zero?
51
+ scrub(scope, cursor: cursor) unless cursor.to_i.zero?
50
52
  end
51
53
  end
52
54
  end
@@ -1,3 +1,3 @@
1
1
  module Verdict
2
- VERSION = "0.11.0"
2
+ VERSION = "0.12.0"
3
3
  end
@@ -424,7 +424,6 @@ class ExperimentTest < Minitest::Test
424
424
  end
425
425
 
426
426
  def test_cleanup
427
- redis = ::Redis.new(host: REDIS_HOST, port: REDIS_PORT)
428
427
  storage = Verdict::Storage::RedisStorage.new(redis)
429
428
  experiment = Verdict::Experiment.new(:cleanup_test) do
430
429
  groups { group :all, 100 }
@@ -440,6 +439,16 @@ class ExperimentTest < Minitest::Test
440
439
  redis.del("experiments/cleanup_test")
441
440
  end
442
441
 
442
+ def test_cleanup_options
443
+ experiment = Verdict::Experiment.new(:cleanup_test) do
444
+ groups { group :all, 100 }
445
+ end
446
+
447
+ experiment.storage.expects(:clear).with(experiment.handle, some: :thing)
448
+ experiment.assign("something")
449
+ experiment.cleanup(some: :thing)
450
+ end
451
+
443
452
  def test_cleanup_without_redis
444
453
  experiment = Verdict::Experiment.new(:cleanup_test) do
445
454
  groups { group :all, 100 }
@@ -450,4 +459,10 @@ class ExperimentTest < Minitest::Test
450
459
  experiment.cleanup
451
460
  end
452
461
  end
462
+
463
+ private
464
+
465
+ def redis
466
+ @redis ||= ::Redis.new(host: REDIS_HOST, port: REDIS_PORT)
467
+ end
453
468
  end
@@ -28,5 +28,6 @@ class ExperimentTest < Minitest::Test
28
28
  json = JSON.parse(Verdict.repository.to_json)
29
29
  assert_equal ['test_1', 'test_2'], json.keys
30
30
  assert_equal json['test_1'], JSON.parse(e1.to_json)
31
+ assert_equal json['test_2'], JSON.parse(e2.to_json)
31
32
  end
32
33
  end
data/test/fake_app.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'rails'
1
+ require "rails"
2
2
  require "active_model/railtie"
3
3
  require "action_controller/railtie"
4
4
  require "action_view/railtie"
@@ -35,9 +35,9 @@ class MemoryStorageTest < Minitest::Test
35
35
  end
36
36
 
37
37
  def test_started_at
38
- assert @storage.get(@experiment.handle.to_s, 'started_at').nil?
38
+ assert @storage.send(:get, @experiment.handle.to_s, 'started_at').nil?
39
39
  @experiment.send(:ensure_experiment_has_started)
40
- refute @storage.get(@experiment.handle.to_s, 'started_at').nil?
40
+ refute @storage.send(:get, @experiment.handle.to_s, 'started_at').nil?
41
41
  assert_instance_of Time, @experiment.started_at
42
42
  end
43
43
  end
@@ -71,17 +71,29 @@ class RedisStorageTest < Minitest::Test
71
71
  assert_equal a, @experiment.started_at
72
72
  end
73
73
 
74
- def test_cleanup
74
+ def test_cleanup_with_scope_argument
75
75
  1000.times do |n|
76
76
  @experiment.assign("something_#{n}")
77
77
  end
78
78
 
79
79
  assert_operator @redis, :exists, experiment_key
80
80
 
81
+ Verdict.default_logger.expects(:warn).with(regexp_matches(/deprecated/))
81
82
  @storage.cleanup(:redis_storage)
82
83
  refute_operator @redis, :exists, experiment_key
83
84
  end
84
85
 
86
+ def test_cleanup
87
+ 1000.times do |n|
88
+ @experiment.assign("something_#{n}")
89
+ end
90
+
91
+ assert_operator @redis, :exists, experiment_key
92
+
93
+ @storage.cleanup(@experiment)
94
+ refute_operator @redis, :exists, experiment_key
95
+ end
96
+
85
97
  private
86
98
 
87
99
  def experiment_key
@@ -5,9 +5,16 @@ class VerdictRailsTest < Minitest::Test
5
5
  def setup
6
6
  Verdict.clear_repository_cache
7
7
  new_rails_app = Dummy::Application.new
8
+ new_rails_app.config.eager_load = false
8
9
  new_rails_app.initialize!
9
10
  end
10
11
 
12
+ def teardown
13
+ Verdict.default_logger = Logger.new("/dev/null")
14
+ Verdict.directory = nil
15
+ Verdict.clear_repository_cache
16
+ end
17
+
11
18
  def test_verdict_railtie_should_find_directory_path
12
19
  assert_equal Verdict.directory, Rails.root.join('app', 'experiments')
13
20
  end
@@ -16,4 +23,4 @@ class VerdictRailsTest < Minitest::Test
16
23
  expected_experiment = Verdict.instance_variable_get('@repository')
17
24
  assert expected_experiment.include?("test_rails_app_experiment")
18
25
  end
19
- end
26
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verdict
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-03 00:00:00.000000000 Z
11
+ date: 2019-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest