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
+ 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