memcache-client-activerecord 0.1.0 → 0.2.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.
- 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
|