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
+ describe Sequel::Unicache::GlobalConfiguration do
2
+ it 'should be true' do
3
+ expect(Sequel::Unicache.config).to be_kind_of Sequel::Unicache::GlobalConfiguration
4
+ end
5
+
6
+ it 'can configure for unicache' do
7
+ logger = Logger.new(STDERR)
8
+
9
+ Sequel::Unicache.configure cache: memcache,
10
+ ttl: 30,
11
+ logger: logger
12
+
13
+ expect(Sequel::Unicache.config.cache).to be memcache
14
+ expect(Sequel::Unicache.config.ttl).to be 30
15
+ expect(Sequel::Unicache.config.enabled).to be true
16
+ expect(Sequel::Unicache.config.logger).to be logger
17
+
18
+ serialize_proc = ->(model, opts) { Marshal.dump model }
19
+ deserialize_proc = ->(cache, opts) { Marshal.load cache }
20
+ key_proc = ->(hash, opts) { "id/#{hash[:id]}" }
21
+
22
+ Sequel::Unicache.config.serialize = serialize_proc
23
+ Sequel::Unicache.config.deserialize = deserialize_proc
24
+ Sequel::Unicache.config.key = key_proc
25
+
26
+ expect(Sequel::Unicache.config.serialize).to be serialize_proc
27
+ expect(Sequel::Unicache.config.deserialize).to be deserialize_proc
28
+ expect(Sequel::Unicache.config.key).to be key_proc
29
+
30
+ expect(Sequel::Unicache.config.to_h).to eq cache: memcache,
31
+ ttl: 30,
32
+ enabled: true,
33
+ logger: logger,
34
+ serialize: serialize_proc,
35
+ deserialize: deserialize_proc,
36
+ key: key_proc
37
+ end
38
+
39
+ it 'can enable & disable unicache feature' do
40
+ Sequel::Unicache.disable
41
+ expect(Sequel::Unicache.enabled?).to be false
42
+ Sequel::Unicache.enable
43
+ expect(Sequel::Unicache.enabled?).to be true
44
+ end
45
+
46
+ it 'can suspend & unsuspend read-through' do
47
+ Sequel::Unicache.suspend_unicache
48
+ expect(Sequel::Unicache.unicache_suspended?).to be true
49
+ Sequel::Unicache.unsuspend_unicache
50
+ expect(Sequel::Unicache.unicache_suspended?).to be false
51
+ Sequel::Unicache.suspend_unicache do
52
+ expect(Sequel::Unicache.unicache_suspended?).to be true
53
+ end
54
+ expect(Sequel::Unicache.unicache_suspended?).to be false
55
+ end
56
+ end
data/spec/log_spec.rb ADDED
@@ -0,0 +1,28 @@
1
+ describe Sequel::Unicache::Logger do
2
+ let!(:logger) { Logger.new STDOUT }
3
+ let!(:user_id) { User.first.id }
4
+ before :each do
5
+ Sequel::Unicache.configure cache: memcache, logger: logger
6
+ end
7
+
8
+ context 'read through' do
9
+ it 'should log down and ignore exception if failed to serialize' do
10
+ User.instance_exec { unicache :id, serialize: ->(values, _) { raise 'test' } }
11
+ expect(logger).to receive(:error).at_least(:once)
12
+ user = User[user_id]
13
+ cache = memcache.get "User:id:#{user.id}"
14
+ expect(cache).to be_nil
15
+ end
16
+ end
17
+
18
+ context 'expire' do
19
+ it 'should log down, ignore exception when failed to expire during model destroy' do
20
+ user = User[user_id]
21
+ cache = memcache.get "User:id:#{user.id}"
22
+ expect(cache).not_to be_nil
23
+ User.instance_exec { unicache :id, key: ->(values, _) { raise 'test' } }
24
+ expect(logger).to receive(:fatal).at_least(:once)
25
+ user.destroy
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ servers:
2
+ - host: localhost
3
+ port: 11211
4
+ options:
5
+ namespace: sequel
6
+ compress: true
7
+ threadsafe: true
8
+ failover: true
@@ -0,0 +1,96 @@
1
+ require 'bundler'
2
+
3
+ begin
4
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', __dir__)
5
+ Bundler.setup
6
+ rescue Bundler::GemNotFound
7
+ abort "Bundler couldn't find some gems.\n" \
8
+ 'Did you run `bundle install`?'
9
+ end
10
+
11
+ require 'erb'
12
+ require 'logger'
13
+ require 'json'
14
+ require 'yaml'
15
+
16
+ require 'sequel/unicache'
17
+ require 'active_support/core_ext/hash/keys'
18
+ require 'dalli'
19
+ require 'pry'
20
+
21
+ module Helpers
22
+ def memcache
23
+ @cache ||= begin
24
+ memcache_config = YAML.load(ERB.new(File.read(File.expand_path('memcache.yml', __dir__))).result)
25
+
26
+ hosts = memcache_config['servers'].map {|server| "#{server['host']}:#{server['port']}" }
27
+ opts = memcache_config['options'] || {}
28
+
29
+ client = Dalli::Client.new hosts, opts.symbolize_keys
30
+ client.alive!
31
+ client
32
+ rescue Dalli::RingError
33
+ abort "Memcache Server is unavailable."
34
+ rescue Errno::ENOENT
35
+ abort "You must configure memcache in spec/memcache.yml before the testing.\n" \
36
+ "Copy from spec/memcache.yml.example then modify base on it will be recommended."
37
+ end
38
+ end
39
+
40
+ def initialize_models
41
+ user_class = Class.new Sequel::Model
42
+ user_class.set_dataset database[:users]
43
+ user_class.many_to_one :manager, class: :User
44
+ Object.send :const_set, :User, user_class
45
+ end
46
+
47
+ def clear_models
48
+ Object.send :remove_const, :User
49
+ end
50
+
51
+ def database
52
+ @database ||= begin
53
+ db = Sequel.sqlite
54
+ db.run <<-SQL
55
+ CREATE TABLE users(id INTEGER PRIMARY KEY AUTOINCREMENT, manager_id INTEGER,
56
+ username VARCHAR NOT NULL, password VARCHAR,
57
+ company_name VARCHAR NOT NULL, department VARCHAR NOT NULL,
58
+ employee_id INTEGER NOT NULL, created_at DEFAULT CURRENT_TIMESTAMP,
59
+ FOREIGN KEY(manager_id) REFERENCES users(id));
60
+ CREATE UNIQUE INDEX uniq_username ON users(username);
61
+ CREATE UNIQUE INDEX uniq_employee ON users(company_name, department, employee_id);
62
+ SQL
63
+ user_id = db[:users].insert username: 'bachue@gmail.com', password: 'bachue',
64
+ company_name: 'EMC', department: 'Mozy', employee_id: 12345
65
+ boss_id = db[:users].insert username: 'gimi@emc.com', password: 'gimi',
66
+ company_name: 'EMC', department: 'Mozy', employee_id: 10000
67
+ db[:users].where(id: user_id).update manager_id: boss_id
68
+ db
69
+ end
70
+ end
71
+
72
+ def reset_database
73
+ return unless @database
74
+ @database = nil
75
+ end
76
+
77
+ def reset_global_configuration
78
+ Sequel::Unicache.instance_variable_set :@config, Sequel::Unicache::GlobalConfiguration.new
79
+ Sequel::Unicache.enable
80
+ end
81
+ end
82
+
83
+ RSpec.configure do |config|
84
+ config.include Helpers
85
+
86
+ config.before :each do
87
+ memcache.flush_all
88
+ initialize_models
89
+ end
90
+
91
+ config.after :each do
92
+ reset_database
93
+ clear_models
94
+ reset_global_configuration
95
+ end
96
+ end
@@ -0,0 +1,277 @@
1
+ describe Sequel::Unicache::Write do
2
+ let!(:user_id) { User.first.id }
3
+
4
+ before :each do
5
+ Sequel::Unicache.configure cache: memcache
6
+ end
7
+
8
+ context 'read through' do
9
+ it 'should read through cache into memcache' do
10
+ user = User[user_id]
11
+ cache = memcache.get "User:id:#{user.id}"
12
+ expect(cache).not_to be_nil
13
+ expect(Marshal.load(cache)).to eq user.values
14
+ end
15
+
16
+ it 'should serialize model into specified format' do
17
+ User.instance_exec { unicache :id, serialize: ->(values, _) { values.to_yaml } }
18
+ user = User[user_id]
19
+ cache = memcache.get "User:id:#{user.id}"
20
+ expect(cache).not_to be_nil
21
+ expect(YAML.load(cache)).to eq user.values
22
+ end
23
+
24
+ it 'should not read through cache if unicache is not enabled for this key' do
25
+ User.instance_exec { unicache :id, enabled: false }
26
+ user = User[user_id]
27
+ cache = memcache.get "User:id:#{user.id}"
28
+ expect(cache).to be_nil
29
+ user = User.find user_id
30
+ cache = memcache.get "User:id:#{user.id}"
31
+ expect(cache).to be_nil
32
+ end
33
+
34
+ it 'should not read through cache if unicache is not enabled' do
35
+ User.instance_exec { unicache :id, enabled: true }
36
+ Sequel::Unicache.disable
37
+ user = User[user_id]
38
+ cache = memcache.get "User:id:#{user.id}"
39
+ expect(cache).to be_nil
40
+ user = User.find user_id
41
+ cache = memcache.get "User:id:#{user.id}"
42
+ expect(cache).to be_nil
43
+ end
44
+
45
+ it 'should not read through cache if read-through is suspended' do
46
+ User.instance_exec { unicache :id, enabled: true }
47
+ user = Sequel::Unicache.suspend_unicache { User[user_id] }
48
+ cache = memcache.get "User:id:#{user.id}"
49
+ expect(cache).to be_nil
50
+ user = Sequel::Unicache.suspend_unicache { User.find user_id }
51
+ cache = memcache.get "User:id:#{user.id}"
52
+ expect(cache).to be_nil
53
+ end
54
+
55
+ it 'should not read through cache if condition is not permitted' do
56
+ User.instance_exec { unicache :id, if: ->(model, _) { model.company_name != 'EMC' } }
57
+ user = User[user_id]
58
+ cache = memcache.get "User:id:#{user.id}"
59
+ expect(cache).to be_nil
60
+ end
61
+
62
+ it 'should set expiration time as you wish' do
63
+ User.instance_exec { unicache :id, ttl: 1 }
64
+ user = User[user_id]
65
+ cache = memcache.get "User:id:#{user.id}"
66
+ expect(cache).not_to be_nil
67
+ sleep 1
68
+ cache = memcache.get "User:id:#{user.id}"
69
+ expect(cache).to be_nil
70
+ end
71
+
72
+ it 'should not read from cache when unicache is disabled' do
73
+ User[user_id] # cache data
74
+ Sequel::Unicache.disable
75
+ User[user_id].set(company_name: 'VMware').save
76
+ user = User.find user_id
77
+ expect(user.company_name).to eq 'VMware'
78
+ end
79
+
80
+ it 'should not read from cache when reload' do
81
+ user = User[user_id]
82
+ Sequel::Unicache.disable
83
+ User[user_id].set(company_name: 'VMware').save
84
+ Sequel::Unicache.enable
85
+ cache = memcache.get "User:id:#{user.id}"
86
+ expect(cache).not_to be_nil
87
+ user.reload
88
+ cache = memcache.get "User:id:#{user.id}"
89
+ expect(cache).to be_nil
90
+ expect(user.company_name).to eq 'VMware'
91
+ end
92
+ end
93
+
94
+ context 'expire when update' do
95
+ let!(:user) { User[user_id] }
96
+
97
+ it 'should expire cache' do
98
+ cache = memcache.get "User:id:#{user.id}"
99
+ expect(cache).not_to be_nil
100
+ user.set(company_name: 'VMware').save
101
+ cache = memcache.get "User:id:#{user.id}"
102
+ expect(cache).to be_nil
103
+ user = User[user_id]
104
+ cache = memcache.get "User:id:#{user.id}"
105
+ expect(cache).not_to be_nil
106
+ expect(Marshal.load(cache)).to eq user.values
107
+ end
108
+
109
+ it 'should not expire cache until transaction is committed' do
110
+ User.db.transaction auto_savepoint: true do
111
+ user.set(company_name: 'VMware').save
112
+ cache = memcache.get "User:id:#{user.id}"
113
+ expect(cache).not_to be_nil
114
+ end
115
+ cache = memcache.get "User:id:#{user.id}"
116
+ expect(cache).to be_nil
117
+ end
118
+
119
+ it 'should not expire cache if transaction is rollbacked' do
120
+ origin = user.values.dup
121
+ User.db.transaction rollback: :always do
122
+ user.set(company_name: 'VMware').save
123
+ end
124
+ cache = memcache.get "User:id:#{user.id}"
125
+ expect(cache).not_to be_nil
126
+ expect(Marshal.load(cache)).to eq origin
127
+ end
128
+
129
+ it 'should not expire cache even if unicache is not enabled for that key' do
130
+ User.unicache_for(:id).enabled = false
131
+ origin = user.values.dup
132
+ user.set(company_name: 'VMware').save
133
+ cache = memcache.get "User:id:#{user.id}"
134
+ expect(cache).not_to be_nil
135
+ expect(Marshal.load(cache)).to eq origin
136
+ end
137
+
138
+ it 'should not expire cache even if unicache is not enabled' do
139
+ Sequel::Unicache.disable
140
+ origin = user.values.dup
141
+ user.set(company_name: 'VMware').save
142
+ cache = memcache.get "User:id:#{user.id}"
143
+ expect(cache).not_to be_nil
144
+ expect(Marshal.load(cache)).to eq origin
145
+ end
146
+
147
+ it 'should still expire cache even if read-through is suspended' do
148
+ Sequel::Unicache.suspend_unicache { user.set(company_name: 'VMware').save }
149
+ cache = memcache.get "User:id:#{user.id}"
150
+ expect(cache).to be_nil
151
+ end
152
+
153
+ it 'should still expire all cache even if model is not completed' do
154
+ memcache.flush_all # Clear all cache first
155
+ User.instance_exec { unicache :username, key: ->(values, _) { "User/username/#{values[:username]}" } }
156
+ user = User[user_id]
157
+ cache = memcache.get "User/username/bachue@gmail.com"
158
+ expect(cache).not_to be_nil
159
+ user = User.select(:id, :company_name)[user_id]
160
+ user.set(company_name: 'VMware').save
161
+ cache = memcache.get "User/username/bachue@gmail.com"
162
+ expect(cache).to be_nil
163
+ end
164
+
165
+ it 'should expire obsolate cache if any value of the unicache key is changed' do
166
+ User.instance_exec { unicache :username }
167
+ user = User[user_id]
168
+ User.db.transaction auto_savepoint: true do
169
+ user.set(username: 'bachue@emc.com').save
170
+ user.set(username: 'bachue@vmware.com', company_name: 'VMware').save
171
+ end
172
+ cache = memcache.get "User:username:bachue@gmail.com"
173
+ expect(cache).to be_nil
174
+ end
175
+
176
+ it 'should still get currect value during a transaction' do
177
+ user = User[user_id]
178
+ expect(Sequel::Unicache.unicache_suspended?).to be false
179
+ User.db.transaction auto_savepoint: true do
180
+ expect(Sequel::Unicache.unicache_suspended?).to be true
181
+ user.set(username: 'bachue@emc.com').save
182
+ expect(User[user_id].username).to eq 'bachue@emc.com'
183
+ end
184
+ expect(Sequel::Unicache.unicache_suspended?).to be false
185
+ end
186
+ end
187
+
188
+ context 'expire when delete' do
189
+ let!(:user) { User[user_id] }
190
+
191
+ it 'should expire cache' do
192
+ cache = memcache.get "User:id:#{user.id}"
193
+ expect(cache).not_to be_nil
194
+ user.destroy
195
+ cache = memcache.get "User:id:#{user.id}"
196
+ expect(cache).to be_nil
197
+ end
198
+
199
+ it 'should expire cache' do
200
+ User.instance_exec { unicache :username }
201
+ user = User.select(:id).first
202
+ user.destroy
203
+ cache = memcache.get "User:username:#{user.username}"
204
+ expect(cache).to be_nil
205
+ end
206
+
207
+ it 'should not expire cache until transaction is committed' do
208
+ User.db.transaction auto_savepoint: true do
209
+ user.destroy
210
+ cache = memcache.get "User:id:#{user.id}"
211
+ expect(cache).not_to be_nil
212
+ end
213
+ cache = memcache.get "User:id:#{user.id}"
214
+ expect(cache).to be_nil
215
+ end
216
+
217
+ it 'should not expire cache even if unicache is not enabled for that key' do
218
+ User.unicache_for(:id).enabled = false
219
+ user.destroy
220
+ cache = memcache.get "User:id:#{user.id}"
221
+ expect(cache).not_to be_nil
222
+ expect(Marshal.load(cache)).to eq user.values
223
+ end
224
+
225
+ it 'should not expire cache even if unicache is not enabled' do
226
+ Sequel::Unicache.disable
227
+ user.destroy
228
+ cache = memcache.get "User:id:#{user.id}"
229
+ expect(cache).not_to be_nil
230
+ expect(Marshal.load(cache)).to eq user.values
231
+ end
232
+
233
+ it 'should still expire cache even if read-through is suspended' do
234
+ Sequel::Unicache.suspend_unicache { user.destroy }
235
+ cache = memcache.get "User:id:#{user.id}"
236
+ expect(cache).to be_nil
237
+ end
238
+ end
239
+
240
+ context 'expire by #expire_unicache' do
241
+ let(:user) { User[user_id] }
242
+
243
+ before :each do
244
+ User.instance_exec do
245
+ unicache :username
246
+ unicache :company_name, :department, :employee_id
247
+ end
248
+ end
249
+
250
+ it 'should expire all keys' do
251
+ cache = memcache.get "User:id:#{user.id}"
252
+ expect(cache).not_to be_nil
253
+ cache = memcache.get "User:username:bachue@gmail.com"
254
+ expect(cache).not_to be_nil
255
+ cache = memcache.get "User:company_name:EMC:department:Mozy:employee_id:12345"
256
+ expect(cache).not_to be_nil
257
+ user.expire_unicache
258
+ cache = memcache.get "User:id:#{user.id}"
259
+ expect(cache).to be_nil
260
+ cache = memcache.get "User:username:bachue@gmail.com"
261
+ expect(cache).to be_nil
262
+ cache = memcache.get "User:company_name:EMC:department:Mozy:employee_id:12345"
263
+ expect(cache).to be_nil
264
+ end
265
+
266
+ it 'should expire all keys even unicache is disabled' do
267
+ Sequel::Unicache.disable
268
+ user.expire_unicache
269
+ cache = memcache.get "User:id:#{user.id}"
270
+ expect(cache).to be_nil
271
+ cache = memcache.get "User:username:bachue@gmail.com"
272
+ expect(cache).to be_nil
273
+ cache = memcache.get "User:company_name:EMC:department:Mozy:employee_id:12345"
274
+ expect(cache).to be_nil
275
+ end
276
+ end
277
+ end