sequel-unicache 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ require 'sequel/unicache/write'
2
+
3
+ module Sequel
4
+ module Unicache
5
+ class Hook # Provide after_commit & after_destroy_commit to update cache
6
+ class << self
7
+ def install_hooks_for_unicache
8
+ Sequel::Model.include InstanceMethods
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+ def after_commit
14
+ if Unicache.enabled?
15
+ Write.expire self
16
+ @_unicache_previous_values = nil
17
+ end
18
+ super
19
+ end
20
+
21
+ def after_rollback
22
+ @_unicache_previous_values = nil if Unicache.enabled?
23
+ super
24
+ end
25
+
26
+ def after_destroy_commit
27
+ if Unicache.enabled?
28
+ Write.expire self
29
+ @_unicache_previous_values = nil
30
+ end
31
+ super
32
+ end
33
+
34
+ def after_destroy_rollback
35
+ @_unicache_previous_values = nil if Unicache.enabled?
36
+ super
37
+ end
38
+
39
+ def before_destroy
40
+ if Unicache.enabled? && !Write.check_completeness?(self) && primary_key
41
+ @_unicache_previous_values = self.class.with_pk(pk).values
42
+ end
43
+ super
44
+ end
45
+
46
+ def before_update
47
+ if Unicache.enabled?
48
+ # Store all previous values, to be expired
49
+ @_unicache_previous_values = initial_values.merge(@_unicache_previous_values || {})
50
+ end
51
+ super
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ module Sequel
2
+ module Unicache
3
+ class Logger
4
+ class << self
5
+ %i[debug info warn error fatal unknown].each do |level|
6
+ define_method level do |config, message = nil, &block|
7
+ # config can be treated as a model, then fallback to model class configuration and global configuration
8
+ config = config.class.unicache_class_configuration if config.is_a? Sequel::Model
9
+ config.logger.send level, message, &block if config.logger
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module Sequel
2
+ module Unicache
3
+ class Transaction
4
+ class << self
5
+ def install_hooks_for_unicache
6
+ Sequel::Database.prepend InstanceMethods
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ def _transaction conn, opts = Database::OPTS, &block
12
+ super conn, opts do |conn|
13
+ Unicache.suspend_unicache { block.call conn }
14
+ end
15
+ end
16
+ private :_transaction
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module Sequel
2
+ module Unicache
3
+ VERSION = '0.9.0'
4
+ end
5
+ end
@@ -0,0 +1,122 @@
1
+ require 'sequel/unicache/logger'
2
+
3
+ module Sequel
4
+ module Unicache
5
+ class Write
6
+ class << self
7
+ def write model
8
+ # If model is not completed, don't cache it
9
+ if (model.columns - model.keys).empty?
10
+ cache = {}
11
+ unicache_configurations(model).each_value do |config|
12
+ continue unless enabled? model, config # if unicached is disabled, do nothing
13
+ # write cache requires if-condition returns true
14
+ # otherwise will fallback to expire
15
+ if permitted? model, config
16
+ write_for model, config, cache unless suspended? # must be allowed to write cache
17
+ else
18
+ expire_for model, config
19
+ end
20
+ end
21
+ end
22
+ rescue => error
23
+ Unicache::Logger.fatal model, "[Unicache] Exception happen when write cache for a model, fallback to expire. Reason: #{error.message}. Model: #{model.inspect}"
24
+ error.backtrace.each do |trace|
25
+ Unicache::Logger.fatal model, "[Unicache] #{trace}"
26
+ end
27
+ expire model
28
+ end
29
+
30
+ def expire model, force: false
31
+ configs = unicache_configurations model
32
+ reload model unless check_completeness? model, configs
33
+ restore_previous model do # restore to previous values temporarily
34
+ # Unicache must be enabled then do expiration
35
+ configs.each_value { |config| expire_for model, config if force || enabled?(model, config) }
36
+ end
37
+ rescue => error
38
+ Unicache::Logger.fatal model, "[Unicache] Exception happen when expire cache for a model. Reason: #{error.message}. Model: #{model.inspect}"
39
+ error.backtrace.each do |trace|
40
+ Unicache::Logger.fatal model, "[Unicache] #{trace}"
41
+ end
42
+ end
43
+
44
+ def check_completeness? model, configs = unicache_configurations(model)
45
+ all_unicache_keys = configs.keys.flatten.uniq
46
+ model_keys = model.keys
47
+ all_unicache_keys.all? {|key| model_keys.include? key }
48
+ end
49
+
50
+ private
51
+
52
+ def reload model
53
+ model.reload
54
+ rescue Sequel::Error => err
55
+ raise unless err.message == 'Record not found'
56
+ end
57
+
58
+ def write_for model, config, results
59
+ key = cache_key model, config
60
+ cache = results[config.serialize]
61
+ unless cache # if serialize was run before, use the cache
62
+ cache = config.serialize.(model.values, config)
63
+ results[config.serialize] = cache
64
+ end
65
+ config.cache.set key, cache, config.ttl
66
+ rescue => error
67
+ Unicache::Logger.error config, "[Unicache] Exception happen when write cache for unicache_key, fallback to expire. Reason: #{error.message}. Model: #{model.inspect}. Config: #{config.inspect}"
68
+ error.backtrace.each do |trace|
69
+ Unicache::Logger.error config, "[Unicache] #{trace}"
70
+ end
71
+ expire_for model, config
72
+ end
73
+
74
+ def expire_for model, config
75
+ key = cache_key model, config
76
+ config.cache.delete key
77
+ rescue => error
78
+ Unicache::Logger.fatal config, "[Unicache] Exception happen when expire cache for unicache_key. Reason: #{error.message}. Model: #{model.inspect}. Config: #{config.inspect}"
79
+ error.backtrace.each do |trace|
80
+ Unicache::Logger.fatal config, "[Unicache] #{trace}"
81
+ end
82
+ end
83
+
84
+ def unicache_configurations model
85
+ model.class.unicache_configurations
86
+ end
87
+
88
+ def cache_key model, config
89
+ values = select_keys model, config.unicache_keys
90
+ config.key.(values, config)
91
+ end
92
+
93
+ def restore_previous model
94
+ previous_changes = model.instance_variable_get :@_unicache_previous_values
95
+ unless previous_changes.nil? || previous_changes.empty?
96
+ origin = select_keys model, previous_changes.keys
97
+ model.set_all previous_changes
98
+ end
99
+ yield
100
+ ensure
101
+ model.set_all origin if origin
102
+ end
103
+
104
+ def select_keys model, keys
105
+ Array(keys).inject({}) { |hash, attr| hash.merge attr => model[attr] }
106
+ end
107
+
108
+ def enabled? model, config
109
+ model.class.unicache_enabled_for? config
110
+ end
111
+
112
+ def suspended?
113
+ Unicache.unicache_suspended?
114
+ end
115
+
116
+ def permitted? model, config
117
+ !config.if || config.if.(model, config)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sequel/unicache/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sequel-unicache"
8
+ spec.version = Sequel::Unicache::VERSION
9
+ spec.authors = ['Bachue Zhou']
10
+ spec.email = ['bachue.shu@gmail.com']
11
+ spec.summary = 'Write through and Read through caching library inspired by Cache Money, support Sequel 4'
12
+ spec.description = <<-SUMMARY
13
+ Read through caching library inspired by Cache Money, support Sequel 4
14
+
15
+ Read-Through: Queries by ID or any specified unique key, like `User[params[:id]]` or `User[username: 'bachue@gmail.com']`, will first look in memcache store and then look in the database for the results of that query. If there is a cache miss, it will populate the cache. As objects are created, updated, and deleted, all of the caches are automatically expired.
16
+ SUMMARY
17
+ spec.homepage = 'https://github.com/bachue/sequel-unicache'
18
+ spec.license = 'GPLv2'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0")
21
+ spec.test_files = spec.files.grep(%r{^spec/})
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.required_ruby_version = '~> 2.1'
25
+ spec.add_runtime_dependency 'sequel', '~> 4.0'
26
+ spec.add_development_dependency 'bundler', '~> 1.7'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'dalli'
29
+ spec.add_development_dependency 'pry'
30
+ spec.add_development_dependency 'pry-doc'
31
+ spec.add_development_dependency 'pry-byebug'
32
+ spec.add_development_dependency 'rspec'
33
+ spec.add_development_dependency 'sqlite3'
34
+ spec.add_development_dependency 'activesupport'
35
+ end
@@ -0,0 +1,121 @@
1
+ describe Sequel::Unicache::Configuration do
2
+ before :each do
3
+ Sequel::Unicache.configure cache: memcache, enabled: true, ttl: 120
4
+ end
5
+
6
+ it 'should configure primary key as unicache' do
7
+ expect(User.unicache_for(:id)).to be_kind_of Sequel::Unicache::Configuration
8
+ expect(User.unicache_for(:id).cache).to be memcache
9
+ expect(User.unicache_for(:id).enabled).to be true
10
+ expect(User.unicache_for(:id).ttl).to be 120
11
+ expect(User.unicache_for(:id).model_class).to be User
12
+ expect(User.unicache_for(:id).unicache_keys).to be :id
13
+ end
14
+
15
+ it 'should let class-level configuration override global-level configuration' do
16
+ User.instance_exec { unicache :id, ttl: 150 }
17
+ expect(User.unicache_for(:id).cache).to be memcache
18
+ expect(User.unicache_for(:id).enabled).to be true
19
+ expect(User.unicache_for(:id).ttl).to be 150
20
+ expect(User.unicache_for(:id).model_class).to be User
21
+ expect(User.unicache_for(:id).unicache_keys).to be :id
22
+ end
23
+
24
+ it 'should configure primary key as unicache even primary key is changed' do
25
+ User.set_primary_key [:company_name, :department, :employee_id]
26
+ expect(User.unicache_for(:company_name, :department, :employee_id)).to be_kind_of Sequel::Unicache::Configuration
27
+ expect(User.unicache_for(:department, :company_name, :employee_id).cache).to be memcache
28
+ expect(User.unicache_for(:employee_id, :department, :company_name).enabled).to be true
29
+ expect(User.unicache_for(:department, :employee_id, :company_name).ttl).to be 120
30
+ expect(User.unicache_for(:company_name, :employee_id, :department).model_class).to be User
31
+ expect(User.unicache_for(:employee_id, :company_name, :department).unicache_keys).to eq [:company_name, :department, :employee_id]
32
+ end
33
+
34
+ it 'can configure for primary key manually' do
35
+ condition_proc = ->(model, opts) { model.deleted? }
36
+ User.instance_exec { unicache :id, enabled: false, if: condition_proc }
37
+ expect(User.unicache_for(:id)).to be_kind_of Sequel::Unicache::Configuration
38
+ expect(User.unicache_for(:id).cache).to be memcache
39
+ expect(User.unicache_for(:id).enabled).to be false
40
+ expect(User.unicache_for(:id).ttl).to be 120
41
+ expect(User.unicache_for(:id).if).to be condition_proc
42
+ expect(User.unicache_for(:id).model_class).to be User
43
+ expect(User.unicache_for(:id).unicache_keys).to be :id
44
+ end
45
+
46
+ it 'can configure for another unicache' do
47
+ serialize_proc = ->(values, opts) { JSON.dump values }
48
+ deserialize_proc = ->(cache, opts) { JSON.load cache }
49
+ User.instance_exec {
50
+ unicache :department, :employee_id, :company_name, ttl: 60,
51
+ serialize: serialize_proc, deserialize: deserialize_proc
52
+ }
53
+ expect(User.unicache_for(:company_name, :department, :employee_id)).to be_kind_of Sequel::Unicache::Configuration
54
+ expect(User.unicache_for(:department, :company_name, :employee_id).cache).to be memcache
55
+ expect(User.unicache_for(:employee_id, :department, :company_name).enabled).to be true
56
+ expect(User.unicache_for(:department, :employee_id, :company_name).serialize).to be serialize_proc
57
+ expect(User.unicache_for(:company_name, :employee_id, :department).deserialize).to be deserialize_proc
58
+ expect(User.unicache_for(:employee_id, :company_name, :department).model_class).to be User
59
+ expect(User.unicache_for(:company_name, :department, :employee_id).unicache_keys).to eq [:company_name, :department, :employee_id]
60
+ end
61
+
62
+ it 'can configure key-level, class-level and global level' do
63
+ condition_proc = ->(model, opts) { model.deleted? }
64
+ serialize_proc = ->(values, opts) { JSON.dump values }
65
+ deserialize_proc = ->(cache, opts) { JSON.load cache }
66
+ User.instance_exec {
67
+ unicache if: condition_proc, enabled: false
68
+ unicache :department, :employee_id, :company_name, ttl: 60, enabled: true,
69
+ serialize: serialize_proc, deserialize: deserialize_proc
70
+ }
71
+ expect(User.unicache_for(:id)).to be_kind_of Sequel::Unicache::Configuration
72
+ expect(User.unicache_for(:id).cache).to be memcache
73
+ expect(User.unicache_for(:id).enabled).to be false
74
+ expect(User.unicache_for(:id).if).to be condition_proc
75
+ expect(User.unicache_for(:id).ttl).to be 120
76
+ expect(User.unicache_for(:id).model_class).to be User
77
+ expect(User.unicache_for(:id).unicache_keys).to be :id
78
+ expect(User.unicache_for(:company_name, :department, :employee_id)).to be_kind_of Sequel::Unicache::Configuration
79
+ expect(User.unicache_for(:department, :company_name, :employee_id).cache).to be memcache
80
+ expect(User.unicache_for(:employee_id, :department, :company_name).enabled).to be true
81
+ expect(User.unicache_for(:employee_id, :department, :company_name).ttl).to be 60
82
+ expect(User.unicache_for(:department, :employee_id, :company_name).serialize).to be serialize_proc
83
+ expect(User.unicache_for(:company_name, :employee_id, :department).deserialize).to be deserialize_proc
84
+ expect(User.unicache_for(:employee_id, :company_name, :department).model_class).to be User
85
+ expect(User.unicache_for(:company_name, :department, :employee_id).unicache_keys).to eq [:company_name, :department, :employee_id]
86
+ end
87
+
88
+ it 'can enable & disable any unicache key' do
89
+ User.instance_exec { unicache enabled: false }
90
+ expect(User.unicache_for(:id).enabled).to be false
91
+ expect(User.unicache_enabled_for?(:id)).to be false
92
+ User.unicache_for(:id).enabled = true
93
+ expect(User.unicache_for(:id).enabled).to be true
94
+ expect(User.unicache_enabled_for?(:id)).to be true
95
+ end
96
+
97
+ it 'can fuzzy search for unicache keys' do
98
+ User.instance_exec { unicache :department, :employee_id, :company_name }
99
+ expect(User.unicache_for(:id, :department)).to be_nil
100
+ expect(User.unicache_for(:id, :department, fuzzy: true)).to be User.unicache_for(:id)
101
+ expect(User.unicache_for(:id, :department, :employee_id, :company_name, fuzzy: true)).to be User.unicache_for(:id)
102
+ User.unicache_for(:id).enabled = false
103
+ expect(User.unicache_for(:id, :department, fuzzy: true)).to be_nil
104
+ expect(User.unicache_for(:id, :department, :employee_id, :company_name, fuzzy: true)).to be User.unicache_for(:department, :employee_id, :company_name)
105
+ end
106
+
107
+ it 'should disable unicache key if cache store is not specified' do
108
+ reset_global_configuration
109
+ Sequel::Unicache.configure enabled: true, ttl: 120
110
+ expect(User.unicache_enabled_for?(:id)).to be nil
111
+ cache = memcache
112
+ User.instance_exec { unicache :id, cache: cache }
113
+ expect(User.unicache_enabled_for?(:id)).to be true
114
+ end
115
+
116
+ it 'should always give default value for serialize, deserialize & key' do
117
+ expect(User.unicache_for(:id).serialize).not_to be_nil
118
+ expect(User.unicache_for(:id).deserialize).not_to be_nil
119
+ expect(User.unicache_for(:id).key).not_to be_nil
120
+ end
121
+ end
@@ -0,0 +1,173 @@
1
+ describe Sequel::Unicache::Finder do
2
+ let!(:user_id) { User.first.id }
3
+
4
+ before :each do
5
+ Sequel::Unicache.configure cache: memcache
6
+ end
7
+
8
+ context '.[]' do
9
+ context 'simple pk' do
10
+ it 'should cache' do
11
+ user = User[user_id]
12
+ cache = memcache.get "User:id:#{user.id}"
13
+ expect(cache).not_to be_nil
14
+ expect(Marshal.load(cache)).to eq user.values
15
+ end
16
+
17
+ it 'should get model from cache' do
18
+ User.instance_exec { unicache :id, serialize: ->(values, _) { values.to_yaml }, deserialize: ->(values, _) { YAML.load values } }
19
+ expect(User[10]).to be_nil
20
+ values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC', employee_id: 1000 }
21
+ memcache.set 'User:id:10', values.to_yaml
22
+ user = User[10]
23
+ expect(user).not_to be_nil
24
+ expect(user.values).to eq values
25
+ end
26
+ end
27
+
28
+ context 'single unicache key' do
29
+ before :each do
30
+ User.instance_exec { unicache :username }
31
+ end
32
+
33
+ it 'should cache' do
34
+ user = User[username: 'bachue@gmail.com']
35
+ cache = memcache.get 'User:username:bachue@gmail.com'
36
+ expect(cache).not_to be_nil
37
+ expect(Marshal.load(cache)).to eq user.values
38
+ end
39
+
40
+ it 'should get model from cache' do
41
+ expect(User[username: 'bachue@emc.com']).to be_nil
42
+ values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC', employee_id: 1000 }
43
+ memcache.set 'User:username:bachue@emc.com', Marshal.dump(values)
44
+ user = User[username: 'bachue@emc.com']
45
+ expect(user).not_to be_nil
46
+ expect(user.values).to eq values
47
+ end
48
+ end
49
+
50
+ context 'complexed unicache key' do
51
+ before :each do
52
+ User.instance_exec { unicache :company_name, :department, :employee_id }
53
+ end
54
+
55
+ it 'should cache' do
56
+ user = User[company_name: 'EMC', department: 'Mozy', employee_id: 12345]
57
+ cache = memcache.get 'User:company_name:EMC:department:Mozy:employee_id:12345'
58
+ expect(cache).not_to be_nil
59
+ expect(Marshal.load(cache)).to eq user.values
60
+ end
61
+
62
+ it 'should get model from cache' do
63
+ expect(User[company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000]).to be_nil
64
+ values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000 }
65
+ memcache.set 'User:company_name:EMC:department:DPC\:Mozy:employee_id:1000', Marshal.dump(values)
66
+ user = User[company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000]
67
+ expect(user).not_to be_nil
68
+ expect(user.values).to eq values
69
+ end
70
+ end
71
+
72
+ context 'negative cases' do
73
+ it 'should only read-through from simple dataset' do
74
+ User[user_id] # cache primary key
75
+ expect(User.where(department: 'DPC')[user_id]).to be_nil
76
+ expect(User.offset(1)[user_id]).to be_nil
77
+ end
78
+
79
+ it 'should not read-through from joined dataset' do
80
+ user = User[1] # cache primary key
81
+ ds = User.inner_join :users, {id: :manager_id}, table_alias: 'managers'
82
+ expect(ds[1].values).not_to eq user.values
83
+ end
84
+ end
85
+ end
86
+
87
+ context '.find' do
88
+ context 'simple pk' do
89
+ it 'should cache' do
90
+ user = User.find user_id
91
+ cache = memcache.get "User:id:#{user.id}"
92
+ expect(cache).not_to be_nil
93
+ expect(Marshal.load(cache)).to eq user.values
94
+ end
95
+
96
+ it 'should get model from cache' do
97
+ User.instance_exec { unicache :id, serialize: ->(values, _) { values.to_yaml }, deserialize: ->(values, _) { YAML.load values } }
98
+ expect(User.find 10).to be_nil
99
+ values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC', employee_id: 1000 }
100
+ memcache.set 'User:id:10', values.to_yaml
101
+ user = User.find 10
102
+ expect(user).not_to be_nil
103
+ expect(user.values).to eq values
104
+ end
105
+ end
106
+
107
+ context 'single unicache key' do
108
+ before :each do
109
+ User.instance_exec { unicache :username }
110
+ end
111
+
112
+ it 'should cache' do
113
+ user = User.find username: 'bachue@gmail.com'
114
+ cache = memcache.get "User:username:bachue@gmail.com"
115
+ expect(cache).not_to be_nil
116
+ expect(Marshal.load(cache)).to eq user.values
117
+ end
118
+
119
+ it 'should get model from cache' do
120
+ User.instance_exec { unicache :username, serialize: ->(values, _) { values.to_yaml }, deserialize: ->(values, _) { YAML.load values } }
121
+ expect(User.find(username: 'bachue@emc.com')).to be_nil
122
+ values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC', employee_id: 1000 }
123
+ memcache.set 'User:username:bachue@emc.com', values.to_yaml
124
+ user = User.find username: 'bachue@emc.com'
125
+ expect(user).not_to be_nil
126
+ expect(user.values).to eq values
127
+ end
128
+ end
129
+
130
+ context 'complexed unicache key' do
131
+ before :each do
132
+ User.instance_exec { unicache :company_name, :department, :employee_id }
133
+ end
134
+
135
+ it 'should cache' do
136
+ user = User.find company_name: 'EMC', department: 'Mozy', employee_id: 12345
137
+ cache = memcache.get 'User:company_name:EMC:department:Mozy:employee_id:12345'
138
+ expect(cache).not_to be_nil
139
+ expect(Marshal.load(cache)).to eq user.values
140
+ end
141
+
142
+ it 'should get model from cache' do
143
+ expect(User.find company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000).to be_nil
144
+ values = { id: 10, username: 'bachue@emc.com', password: '123456', company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000 }
145
+ memcache.set 'User:company_name:EMC:department:DPC\:Mozy:employee_id:1000', Marshal.dump(values)
146
+ user = User.find company_name: 'EMC', department: 'DPC:Mozy', employee_id: 1000
147
+ expect(user).not_to be_nil
148
+ expect(user.values).to eq values
149
+ end
150
+ end
151
+ end
152
+
153
+ context 'association' do
154
+ let!(:user) { User.first }
155
+
156
+ it 'should cache associated object' do
157
+ manager = user.manager
158
+ cache = memcache.get "User:id:#{manager.id}"
159
+ expect(cache).not_to be_nil
160
+ expect(Marshal.load(cache)).to eq manager.values
161
+ end
162
+
163
+ it 'should get model from cache' do
164
+ expect(user.manager.manager).to be_nil
165
+ values = { id: 10, username: 'tim@emc.com', password: 'abcdef', company_name: 'EMC', department: 'DPC:Mozy', employee_id: 100 }
166
+ memcache.set "User:id:10", Marshal.dump(values)
167
+ user.manager.set manager_id: 10
168
+ manager = user.manager.manager
169
+ expect(manager).not_to be_nil
170
+ expect(manager.values).to eq values
171
+ end
172
+ end
173
+ end