memcache-client-activerecord 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +4 -0
- data/VERSION +1 -1
- data/generators/cache_model/templates/migration.rb +8 -8
- data/lib/memcache/activerecord.rb +61 -56
- data/spec/memcache/activerecord_spec.rb +21 -0
- metadata +3 -3
data/Rakefile
CHANGED
@@ -10,12 +10,16 @@ begin
|
|
10
10
|
gem.email = "m.ishihara@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/m4i/memcache-client-activerecord"
|
12
12
|
gem.authors = ["ISHIHARA Masaki"]
|
13
|
+
gem.rubyforge_project = "mc-activerecord"
|
13
14
|
gem.add_runtime_dependency "activerecord", ">= 2.1"
|
14
15
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
15
16
|
gem.add_development_dependency "memcache-client", ">= 1.7.7"
|
16
17
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
18
|
end
|
18
19
|
Jeweler::GemcutterTasks.new
|
20
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
21
|
+
rubyforge.doc_task = "rdoc"
|
22
|
+
end
|
19
23
|
rescue LoadError
|
20
24
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
25
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -4,19 +4,19 @@ class <%= migration_name %> < ActiveRecord::Migration
|
|
4
4
|
when 'MySQL'
|
5
5
|
execute(<<-SQL)
|
6
6
|
CREATE TABLE `<%= table_name %>` (
|
7
|
-
`key`
|
8
|
-
`value`
|
9
|
-
`cas`
|
10
|
-
`
|
7
|
+
`key` VARBINARY(250) NOT NULL PRIMARY KEY,
|
8
|
+
`value` MEDIUMBLOB NOT NULL,
|
9
|
+
`cas` INT UNSIGNED NOT NULL,
|
10
|
+
`expiry` INT
|
11
11
|
) ENGINE=InnoDB
|
12
12
|
SQL
|
13
13
|
|
14
14
|
else
|
15
15
|
create_table :<%= table_name %>, :id => false do |t|
|
16
|
-
t.string
|
17
|
-
t.binary
|
18
|
-
t.integer
|
19
|
-
t.
|
16
|
+
t.string :key, :null => false, :limit => 250
|
17
|
+
t.binary :value, :null => false
|
18
|
+
t.integer :cas, :null => false
|
19
|
+
t.integer :expiry
|
20
20
|
end
|
21
21
|
add_index :<%= table_name %>, :key, :unique => true
|
22
22
|
end
|
@@ -17,12 +17,13 @@ class MemCache
|
|
17
17
|
|
18
18
|
MAX_KEY_SIZE = 250
|
19
19
|
MAX_VALUE_SIZE = 2 ** 20
|
20
|
+
THIRTY_DAYS = 60 * 60 * 24 * 30
|
20
21
|
|
21
22
|
COLUMN_NAMES = {
|
22
|
-
:key
|
23
|
-
:value
|
24
|
-
:cas
|
25
|
-
:
|
23
|
+
:key => 'key',
|
24
|
+
:value => 'value',
|
25
|
+
:cas => 'cas',
|
26
|
+
:expiry => 'expiry',
|
26
27
|
}
|
27
28
|
|
28
29
|
STORED = "STORED\r\n"
|
@@ -82,7 +83,7 @@ class MemCache
|
|
82
83
|
|
83
84
|
def get(key, raw = false)
|
84
85
|
cache_key = make_cache_key(key)
|
85
|
-
if value = find(cache_key, :value, true
|
86
|
+
if value = find(__method__, cache_key, :value, true)
|
86
87
|
raw ? value : Marshal.load(value)
|
87
88
|
end
|
88
89
|
end
|
@@ -103,7 +104,7 @@ class MemCache
|
|
103
104
|
cache_keys[make_cache_key(key)] = key
|
104
105
|
cache_keys
|
105
106
|
end
|
106
|
-
rows = find_all(cache_keys.keys, [:key, :value], true
|
107
|
+
rows = find_all(__method__, cache_keys.keys, [:key, :value], true)
|
107
108
|
rows.inject({}) do |hash, (key, value)|
|
108
109
|
hash[cache_keys[key]] = Marshal.load(value)
|
109
110
|
hash
|
@@ -116,9 +117,9 @@ class MemCache
|
|
116
117
|
cache_key = make_cache_key(key)
|
117
118
|
value = value_to_storable(value, raw)
|
118
119
|
|
119
|
-
unless update(cache_key, value, expiry
|
120
|
+
unless update(__method__, cache_key, value, expiry)
|
120
121
|
# rescue duplicate key error
|
121
|
-
insert(cache_key, value, expiry
|
122
|
+
insert(__method__, cache_key, value, expiry) rescue nil
|
122
123
|
end
|
123
124
|
|
124
125
|
STORED unless @no_reply
|
@@ -128,7 +129,7 @@ class MemCache
|
|
128
129
|
check_readonly!
|
129
130
|
raise MemCacheError, 'A block is required' unless block_given?
|
130
131
|
|
131
|
-
result = cas_with_reply(key, expiry, raw,
|
132
|
+
result = cas_with_reply(__method__, key, expiry, raw, &block)
|
132
133
|
result unless @no_reply
|
133
134
|
end
|
134
135
|
|
@@ -138,18 +139,18 @@ class MemCache
|
|
138
139
|
cache_key = make_cache_key(key)
|
139
140
|
value = value_to_storable(value, raw)
|
140
141
|
|
141
|
-
old_value,
|
142
|
-
find(cache_key, [:value, :
|
142
|
+
old_value, old_expiry =
|
143
|
+
find(__method__, cache_key, [:value, :expiry], false)
|
143
144
|
|
144
|
-
if old_value && available?(
|
145
|
+
if old_value && available?(old_expiry)
|
145
146
|
NOT_STORED unless @no_reply
|
146
147
|
|
147
148
|
else
|
148
149
|
if old_value
|
149
|
-
update(cache_key, value, expiry
|
150
|
+
update(__method__, cache_key, value, expiry)
|
150
151
|
else
|
151
152
|
# rescue duplicate key error
|
152
|
-
insert(cache_key, value, expiry
|
153
|
+
insert(__method__, cache_key, value, expiry) rescue nil
|
153
154
|
end
|
154
155
|
|
155
156
|
STORED unless @no_reply
|
@@ -162,7 +163,7 @@ class MemCache
|
|
162
163
|
cache_key = make_cache_key(key)
|
163
164
|
value = value_to_storable(value, raw)
|
164
165
|
|
165
|
-
if update(cache_key, value, expiry,
|
166
|
+
if update(__method__, cache_key, value, expiry, true)
|
166
167
|
STORED unless @no_reply
|
167
168
|
else
|
168
169
|
NOT_STORED unless @no_reply
|
@@ -192,11 +193,11 @@ class MemCache
|
|
192
193
|
conditions = { COLUMN_NAMES[:key] => cache_key }
|
193
194
|
|
194
195
|
if @no_reply
|
195
|
-
_delete(
|
196
|
+
_delete(__method__, conditions)
|
196
197
|
nil
|
197
198
|
else
|
198
|
-
exists = !!find(cache_key, :key, true
|
199
|
-
_delete(
|
199
|
+
exists = !!find(__method__, cache_key, :key, true)
|
200
|
+
_delete(__method__, conditions)
|
200
201
|
exists ? DELETED : NOT_FOUND
|
201
202
|
end
|
202
203
|
end
|
@@ -213,7 +214,7 @@ class MemCache
|
|
213
214
|
end
|
214
215
|
|
215
216
|
def garbage_collection!
|
216
|
-
_delete(["#{quote_column_name(:
|
217
|
+
_delete(__method__, ["#{quote_column_name(:expiry)} <= ?", now])
|
217
218
|
end
|
218
219
|
|
219
220
|
private
|
@@ -247,6 +248,14 @@ class MemCache
|
|
247
248
|
value
|
248
249
|
end
|
249
250
|
|
251
|
+
def expiry_to_storable(expiry)
|
252
|
+
expiry.zero? ?
|
253
|
+
nil :
|
254
|
+
expiry <= THIRTY_DAYS ?
|
255
|
+
now + expiry :
|
256
|
+
expiry
|
257
|
+
end
|
258
|
+
|
250
259
|
def check_value_size!(value)
|
251
260
|
if @check_size && value.size > MAX_VALUE_SIZE
|
252
261
|
raise MemCacheError,
|
@@ -254,27 +263,27 @@ class MemCache
|
|
254
263
|
end
|
255
264
|
end
|
256
265
|
|
257
|
-
def gets(key, raw
|
266
|
+
def gets(_method_, key, raw)
|
258
267
|
cache_key = make_cache_key(key)
|
259
|
-
value, cas = find(cache_key, [:value, :cas], true
|
268
|
+
value, cas = find(_method_, cache_key, [:value, :cas], true)
|
260
269
|
if cas
|
261
270
|
[raw ? value : Marshal.load(value), cas]
|
262
271
|
end
|
263
272
|
end
|
264
273
|
|
265
|
-
def cas_with_reply(key, expiry, raw,
|
266
|
-
value, cas = gets(key, raw
|
274
|
+
def cas_with_reply(_method_, key, expiry, raw, &block)
|
275
|
+
value, cas = gets(_method_, key, raw)
|
267
276
|
if cas
|
268
277
|
cache_key = make_cache_key(key)
|
269
278
|
value = value_to_storable(yield(value), raw)
|
270
279
|
|
271
|
-
update(cache_key, value, expiry,
|
280
|
+
update(_method_, cache_key, value, expiry, true, cas) ?
|
272
281
|
STORED : EXISTS
|
273
282
|
end
|
274
283
|
end
|
275
284
|
|
276
285
|
# TODO: check value size
|
277
|
-
def append_or_prepend(
|
286
|
+
def append_or_prepend(_method_, key, value)
|
278
287
|
check_readonly!
|
279
288
|
|
280
289
|
cache_key = make_cache_key(key)
|
@@ -283,33 +292,33 @@ class MemCache
|
|
283
292
|
old = quote_column_name(:value)
|
284
293
|
new = quote_value(:value, value)
|
285
294
|
pairs = {
|
286
|
-
:value => concat_sql(*(
|
295
|
+
:value => concat_sql(*(_method_ == :append ? [old, new] : [new, old]))
|
287
296
|
}
|
288
297
|
|
289
298
|
affected_rows = @ar.connection.update(
|
290
299
|
update_sql(cache_key, pairs, true, nil),
|
291
|
-
sql_name(
|
300
|
+
sql_name(_method_)
|
292
301
|
)
|
293
302
|
|
294
303
|
affected_rows > 0 ? STORED : NOT_STORED unless @no_reply
|
295
304
|
end
|
296
305
|
|
297
|
-
def incr_or_decl(
|
306
|
+
def incr_or_decl(_method_, key, amount)
|
298
307
|
check_readonly!
|
299
308
|
|
300
309
|
unless /\A\s*\d+\s*\z/ =~ amount.to_s
|
301
310
|
raise MemCacheError, 'invalid numeric delta argument'
|
302
311
|
end
|
303
|
-
amount =
|
312
|
+
amount = _method_ == :incr ? amount.to_i : - amount.to_i
|
304
313
|
|
305
314
|
value = nil
|
306
315
|
|
307
316
|
count = 0
|
308
317
|
begin
|
309
318
|
count += 1
|
310
|
-
raise MemCacheError, "cannot #{
|
319
|
+
raise MemCacheError, "cannot #{_method_}" if count > 10
|
311
320
|
|
312
|
-
result = cas_with_reply(key, nil, true
|
321
|
+
result = cas_with_reply(_method_, key, nil, true) do |old_value|
|
313
322
|
unless /\A\s*\d+\s*\z/ =~old_value
|
314
323
|
raise MemCacheError,
|
315
324
|
'cannot increment or decrement non-numeric value'
|
@@ -324,7 +333,7 @@ class MemCache
|
|
324
333
|
raise unless @no_reply
|
325
334
|
end
|
326
335
|
|
327
|
-
def find(cache_key, column_keys, only_available
|
336
|
+
def find(_method_, cache_key, column_keys, only_available)
|
328
337
|
result = @ar.connection.send(
|
329
338
|
column_keys.is_a?(Array) ? :select_one : :select_value,
|
330
339
|
select_sql(
|
@@ -332,7 +341,7 @@ class MemCache
|
|
332
341
|
quote_column_name(*Array(column_keys)),
|
333
342
|
only_available
|
334
343
|
),
|
335
|
-
sql_name(
|
344
|
+
sql_name(_method_)
|
336
345
|
)
|
337
346
|
|
338
347
|
(result && column_keys.is_a?(Array)) ?
|
@@ -340,7 +349,7 @@ class MemCache
|
|
340
349
|
result
|
341
350
|
end
|
342
351
|
|
343
|
-
def find_all(cache_keys, column_keys, only_available
|
352
|
+
def find_all(_method_, cache_keys, column_keys, only_available)
|
344
353
|
return [] if cache_keys.empty?
|
345
354
|
|
346
355
|
result = @ar.connection.send(
|
@@ -350,7 +359,7 @@ class MemCache
|
|
350
359
|
quote_column_name(*Array(column_keys)),
|
351
360
|
only_available
|
352
361
|
),
|
353
|
-
sql_name(
|
362
|
+
sql_name(_method_)
|
354
363
|
)
|
355
364
|
|
356
365
|
column_keys.is_a?(Array) ?
|
@@ -358,7 +367,7 @@ class MemCache
|
|
358
367
|
result
|
359
368
|
end
|
360
369
|
|
361
|
-
def insert(cache_key, value, expiry
|
370
|
+
def insert(_method_, cache_key, value, expiry)
|
362
371
|
attributes = attributes_for_update(value, expiry).merge(
|
363
372
|
:key => cache_key,
|
364
373
|
:cas => 0
|
@@ -374,11 +383,11 @@ class MemCache
|
|
374
383
|
"INSERT INTO #{@ar.quoted_table_name}" +
|
375
384
|
" (#{quote_column_name(*column_keys)})" +
|
376
385
|
" VALUES(#{quoted_values.join(', ')})",
|
377
|
-
sql_name(
|
386
|
+
sql_name(_method_)
|
378
387
|
)
|
379
388
|
end
|
380
389
|
|
381
|
-
def update(cache_key, value, expiry,
|
390
|
+
def update(_method_, cache_key, value, expiry, only_available = false, cas = nil)
|
382
391
|
attributes = attributes_for_update(value, expiry)
|
383
392
|
|
384
393
|
pairs = attributes.keys.inject({}) do |pairs, column_key|
|
@@ -388,32 +397,32 @@ class MemCache
|
|
388
397
|
|
389
398
|
@ar.connection.update(
|
390
399
|
update_sql(cache_key, pairs, only_available, cas),
|
391
|
-
sql_name(
|
400
|
+
sql_name(_method_)
|
392
401
|
) > 0
|
393
402
|
end
|
394
403
|
|
395
|
-
def _delete(
|
404
|
+
def _delete(_method_, conditions)
|
396
405
|
@ar.connection.execute(
|
397
406
|
"DELETE FROM #{@ar.quoted_table_name}" +
|
398
407
|
" WHERE #{@ar.send(:sanitize_sql, conditions)}",
|
399
|
-
sql_name(
|
408
|
+
sql_name(_method_)
|
400
409
|
)
|
401
410
|
end
|
402
411
|
|
403
|
-
def truncate(
|
412
|
+
def truncate(_method_)
|
404
413
|
sql = case @ar.connection.adapter_name
|
405
414
|
when 'SQLite'
|
406
415
|
"DELETE FROM #{@ar.quoted_table_name}"
|
407
416
|
else
|
408
417
|
"TRUNCATE TABLE #{@ar.quoted_table_name}"
|
409
418
|
end
|
410
|
-
@ar.connection.execute(sql, sql_name(
|
419
|
+
@ar.connection.execute(sql, sql_name(_method_))
|
411
420
|
end
|
412
421
|
|
413
422
|
def attributes_for_update(value, expiry)
|
414
423
|
attributes = { :value => value }
|
415
424
|
unless expiry.nil?
|
416
|
-
attributes.update(:
|
425
|
+
attributes.update(:expiry => expiry_to_storable(expiry))
|
417
426
|
end
|
418
427
|
attributes
|
419
428
|
end
|
@@ -449,8 +458,8 @@ class MemCache
|
|
449
458
|
|
450
459
|
if only_available
|
451
460
|
conditions.first << ' AND (' +
|
452
|
-
"#{quote_column_name(:
|
453
|
-
" OR #{quote_column_name(:
|
461
|
+
"#{quote_column_name(:expiry)} IS NULL" +
|
462
|
+
" OR #{quote_column_name(:expiry)} > ?" +
|
454
463
|
')'
|
455
464
|
conditions << now
|
456
465
|
end
|
@@ -467,12 +476,12 @@ class MemCache
|
|
467
476
|
end
|
468
477
|
end
|
469
478
|
|
470
|
-
def sql_name(
|
471
|
-
"#{self.class.name}##{
|
479
|
+
def sql_name(_method_)
|
480
|
+
"#{self.class.name}##{_method_}"
|
472
481
|
end
|
473
482
|
|
474
|
-
def now
|
475
|
-
Time.now
|
483
|
+
def now
|
484
|
+
Time.now.to_i
|
476
485
|
end
|
477
486
|
|
478
487
|
def quote_column_name(*column_keys)
|
@@ -486,12 +495,8 @@ class MemCache
|
|
486
495
|
@ar.connection.quote(value, @ar.columns_hash[COLUMN_NAMES[column_key]])
|
487
496
|
end
|
488
497
|
|
489
|
-
def available?(
|
490
|
-
|
491
|
-
end
|
492
|
-
|
493
|
-
def to_time(expire_at)
|
494
|
-
@ar.columns_hash[COLUMN_NAMES[:expire_at]].type_cast(expire_at)
|
498
|
+
def available?(expiry)
|
499
|
+
expiry.nil? || now < expiry.to_i
|
495
500
|
end
|
496
501
|
end
|
497
502
|
|
@@ -138,6 +138,27 @@ describe "#{adapter}:" do
|
|
138
138
|
caches.same(:get, 'FOO').should == 2
|
139
139
|
end
|
140
140
|
|
141
|
+
it 'should support a number of seconds starting from current time' do
|
142
|
+
[
|
143
|
+
1,
|
144
|
+
MemCache::ActiveRecord::THIRTY_DAYS,
|
145
|
+
].each do |expiry|
|
146
|
+
dbcache.set('foo', 1, expiry)
|
147
|
+
cache_class.find_by_key('foo').expiry.should ==
|
148
|
+
Time.now.to_i + expiry
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should support an unix time expiry' do
|
153
|
+
[
|
154
|
+
MemCache::ActiveRecord::THIRTY_DAYS + 1,
|
155
|
+
Time.now.to_i,
|
156
|
+
].each do |expiry|
|
157
|
+
dbcache.set('foo', 1, expiry)
|
158
|
+
cache_class.find_by_key('foo').expiry.should == expiry
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
141
162
|
unless ENV['LOG']
|
142
163
|
it 'should behave like MemCache with over 64KB value' do
|
143
164
|
value = 'a' * (2 ** 16 + 1)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: memcache-client-activerecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ISHIHARA Masaki
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-12-
|
12
|
+
date: 2009-12-31 00:00:00 +09:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -90,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
90
|
version:
|
91
91
|
requirements: []
|
92
92
|
|
93
|
-
rubyforge_project:
|
93
|
+
rubyforge_project: mc-activerecord
|
94
94
|
rubygems_version: 1.3.5
|
95
95
|
signing_key:
|
96
96
|
specification_version: 3
|