sequel-unicache 0.9.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.
@@ -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