cache 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/BENCHMARKS +98 -0
- data/README.md +153 -0
- data/Rakefile +26 -7
- data/cache.gemspec +1 -0
- data/lib/cache.rb +80 -7
- data/lib/cache/storage.rb +109 -30
- data/lib/cache/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/profile/benchmark.rb +10 -10
- data/test/shared_tests.rb +90 -1
- data/test/test_memcached_rails_storage.rb +10 -10
- data/test/test_memcached_storage.rb +10 -10
- metadata +25 -11
- data/README.rdoc +0 -87
data/.gitignore
CHANGED
data/BENCHMARKS
CHANGED
@@ -1,3 +1,101 @@
|
|
1
|
+
# sabshere 3/4/11 v0.2.0
|
2
|
+
|
3
|
+
vidalia:~/github/cache (master) $ ruby test/profile/benchmark.rb
|
4
|
+
Darwin vidalia 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:55:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_I386 i386
|
5
|
+
ruby 1.8.7 (2010-05-25 patchlevel 266) [i686-darwin9.8.0]
|
6
|
+
RUBY_VERSION=ruby-1.8.7-head
|
7
|
+
Ruby 1.8.7p266
|
8
|
+
Loaded memcached 1.2
|
9
|
+
Loaded remix-stash 1.1.3
|
10
|
+
Loaded memcache-client 1.8.5
|
11
|
+
Loaded cache 0.1.2 # i just hadn't released the gem yet
|
12
|
+
Loaded kgio 2.3.2
|
13
|
+
Loaded dalli 1.0.2
|
14
|
+
Loops is 20000
|
15
|
+
Stack depth is 0
|
16
|
+
Small value size is: 13 bytes
|
17
|
+
Large value size is: 4158 bytes
|
18
|
+
No matching processes belonging to you were found
|
19
|
+
user system total real
|
20
|
+
set: cache:dalli:bin 5.720000 1.860000 7.580000 ( 10.301808)
|
21
|
+
set: cache:libm:bin 1.250000 1.270000 2.520000 ( 5.892224)
|
22
|
+
set: dalli:bin 5.430000 1.860000 7.290000 ( 9.966409)
|
23
|
+
set: libm:ascii 0.760000 1.330000 2.090000 ( 5.348975)
|
24
|
+
set: libm:ascii:pipeline 0.280000 0.010000 0.290000 ( 0.297230)
|
25
|
+
set: libm:ascii:udp 0.650000 0.710000 1.360000 ( 3.593454)
|
26
|
+
set: libm:bin 0.640000 1.360000 2.000000 ( 5.285160)
|
27
|
+
set: libm:bin:buffer 0.270000 0.160000 0.430000 ( 1.234561)
|
28
|
+
set: mclient:ascii 11.330000 3.780000 15.110000 ( 16.174666)
|
29
|
+
set: stash:bin 3.420000 1.330000 4.750000 ( 8.016146)
|
30
|
+
|
31
|
+
get: cache:dalli:bin 5.870000 1.950000 7.820000 ( 10.644558)
|
32
|
+
get: cache:libm:bin 1.370000 1.200000 2.570000 ( 5.946502)
|
33
|
+
get: dalli:bin 5.580000 2.070000 7.650000 ( 10.361689)
|
34
|
+
get: libm:ascii 0.980000 1.300000 2.280000 ( 5.455222)
|
35
|
+
get: libm:ascii:pipeline 1.030000 1.590000 2.620000 ( 5.875592)
|
36
|
+
get: libm:ascii:udp 0.820000 0.730000 1.550000 ( 3.515632)
|
37
|
+
get: libm:bin 0.830000 1.330000 2.160000 ( 5.381290)
|
38
|
+
get: libm:bin:buffer 0.900000 1.630000 2.530000 ( 5.761412)
|
39
|
+
get: mclient:ascii 13.630000 3.870000 17.500000 ( 17.912800)
|
40
|
+
get: stash:bin 3.100000 1.340000 4.440000 ( 7.667182)
|
41
|
+
|
42
|
+
delete: cache:dalli:bin 6.270000 2.390000 8.660000 ( 11.313843)
|
43
|
+
delete: cache:libm:bin 2.190000 1.560000 3.750000 ( 7.459292)
|
44
|
+
delete: dalli:bin 5.660000 2.340000 8.000000 ( 10.374507)
|
45
|
+
delete: libm:ascii 1.850000 1.570000 3.420000 ( 6.922356)
|
46
|
+
delete: libm:ascii:pipeline 0.230000 0.010000 0.240000 ( 0.242642)
|
47
|
+
delete: libm:ascii:udp 1.690000 0.930000 2.620000 ( 4.749489)
|
48
|
+
delete: libm:bin 1.780000 1.530000 3.310000 ( 6.768449)
|
49
|
+
delete: libm:bin:buffer 1.890000 1.880000 3.770000 ( 7.149319)
|
50
|
+
delete: mclient:ascii 11.940000 4.040000 15.980000 ( 16.423645)
|
51
|
+
delete:stash:bin => #<NoMethodError: undefined method `delete' for #<Remix::Stash:0x1249824>>
|
52
|
+
|
53
|
+
get-missing: cache:dalli:bin 5.530000 2.140000 7.670000 ( 10.187561)
|
54
|
+
get-missing: cache:libm:bin 2.200000 1.510000 3.710000 ( 6.953625)
|
55
|
+
get-missing: dalli:bin 5.230000 2.160000 7.390000 ( 9.891539)
|
56
|
+
get-missing: libm:ascii 1.960000 1.420000 3.380000 ( 6.459433)
|
57
|
+
get-missing: libm:ascii:pipeline 2.080000 1.860000 3.940000 ( 6.984890)
|
58
|
+
get-missing: libm:ascii:udp 1.730000 0.910000 2.640000 ( 4.582510)
|
59
|
+
get-missing: libm:bin 1.950000 1.470000 3.420000 ( 6.854775)
|
60
|
+
get-missing: libm:bin:buffer 2.060000 1.900000 3.960000 ( 7.180759)
|
61
|
+
get-missing: mclient:ascii 12.350000 4.100000 16.450000 ( 16.986064)
|
62
|
+
get-missing: stash:bin 3.110000 1.410000 4.520000 ( 7.908972)
|
63
|
+
|
64
|
+
set-large: cache:dalli:bin 8.650000 2.110000 10.760000 ( 13.864690)
|
65
|
+
set-large: cache:libm:bin 2.600000 1.450000 4.050000 ( 7.811509)
|
66
|
+
set-large: dalli:bin 8.250000 2.100000 10.350000 ( 13.370233)
|
67
|
+
set-large: libm:ascii 0.890000 1.460000 2.350000 ( 6.069504)
|
68
|
+
set-large: libm:ascii:pipeline 0.570000 0.450000 1.020000 ( 1.232906)
|
69
|
+
set-large: libm:ascii:udp 0.730000 0.850000 1.580000 ( 4.174216)
|
70
|
+
set-large: libm:bin 0.760000 1.510000 2.270000 ( 5.966586)
|
71
|
+
set-large: libm:bin:buffer 0.530000 0.700000 1.230000 ( 2.419153)
|
72
|
+
set-large: mclient:ascii 13.540000 4.250000 17.790000 ( 18.447711)
|
73
|
+
set-large: stash:bin 6.100000 1.500000 7.600000 ( 11.216811)
|
74
|
+
|
75
|
+
get-large: cache:dalli:bin 8.000000 2.480000 10.480000 ( 13.450322)
|
76
|
+
get-large: cache:libm:bin 6.820000 1.490000 8.310000 ( 12.331486)
|
77
|
+
get-large: dalli:bin 7.670000 2.490000 10.160000 ( 13.148162)
|
78
|
+
get-large: libm:ascii 1.740000 1.540000 3.280000 ( 7.256110)
|
79
|
+
get-large: libm:ascii:pipeline 1.870000 1.960000 3.830000 ( 7.723493)
|
80
|
+
get-large: libm:ascii:udp 1.560000 0.980000 2.540000 ( 4.823281)
|
81
|
+
get-large: libm:bin 1.590000 1.590000 3.180000 ( 7.018376)
|
82
|
+
get-large: libm:bin:buffer 1.730000 1.960000 3.690000 ( 7.365047)
|
83
|
+
get-large: mclient:ascii 17.160000 4.890000 22.050000 ( 22.647077)
|
84
|
+
get-large: stash:bin 3.290000 1.440000 4.730000 ( 7.950154)
|
85
|
+
|
86
|
+
hash:jenkins 0.550000 0.000000 0.550000 ( 0.561738)
|
87
|
+
hash:default 0.550000 0.000000 0.550000 ( 0.553209)
|
88
|
+
hash:crc 0.660000 0.000000 0.660000 ( 0.657155)
|
89
|
+
hash:fnv1_32 0.530000 0.010000 0.540000 ( 0.529303)
|
90
|
+
hash:hsieh 0.280000 0.000000 0.280000 ( 0.281685)
|
91
|
+
hash:fnv1_64 1.160000 0.000000 1.160000 ( 1.169524)
|
92
|
+
hash:none 0.310000 0.000000 0.310000 ( 0.313095)
|
93
|
+
hash:murmur 0.450000 0.000000 0.450000 ( 0.454460)
|
94
|
+
hash:md5 0.950000 0.010000 0.960000 ( 0.949226)
|
95
|
+
hash:fnv1a_64 0.590000 0.000000 0.590000 ( 0.590362)
|
96
|
+
hash:fnv1a_32 0.590000 0.000000 0.590000 ( 0.599005)
|
97
|
+
|
98
|
+
|
1
99
|
# sabshere 2/22/11 v0.0.3
|
2
100
|
|
3
101
|
vidalia:~/github/cache (master) $ ruby test/profile/benchmark.rb
|
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# cache
|
2
|
+
|
3
|
+
A unified cache handling interface, inspired by libraries like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html), Perl's [Cache::Cache](http://cpan.uwinnipeg.ca/module/Cache::Cache), and [CHI](http://cpan.uwinnipeg.ca/module/CHI).
|
4
|
+
|
5
|
+
## Supported methods
|
6
|
+
|
7
|
+
It will translate these methods to whatever Redis, Memcached, etc. client you're using:
|
8
|
+
|
9
|
+
@cache.get 'hello'
|
10
|
+
@cache.set 'hello', 'world', 5.minutes
|
11
|
+
@cache.delete 'hello'
|
12
|
+
@cache.flush
|
13
|
+
@cache.exist? 'hello'
|
14
|
+
@cache.reset
|
15
|
+
@cache.fetch 'hello' { 'world' }
|
16
|
+
@cache.cas 'hello' { |current| 'world' }
|
17
|
+
@cache.increment 'high-fives'
|
18
|
+
@cache.decrement 'high-fives'
|
19
|
+
|
20
|
+
Also provided for Rails compatibility:
|
21
|
+
|
22
|
+
@cache.write 'hello', 'world', :expires_in => 5.minutes
|
23
|
+
@cache.read 'hello'
|
24
|
+
@cache.clear
|
25
|
+
@cache.compare_and_swap
|
26
|
+
@cache.read_multi 'hello', 'privyet', 'hallo'
|
27
|
+
|
28
|
+
## Rationale
|
29
|
+
|
30
|
+
I wanted a common interface to a bunch of great Ruby cache clients so I can develop gems (lock_method, cache_method) that accept any of them.
|
31
|
+
|
32
|
+
* I'm tired of rescuing from Memcached::NotFound
|
33
|
+
* I'm tired of forgetting whether it's :expires_in or :ttl
|
34
|
+
* I don't know why we ever started using read/write instead of get/set.
|
35
|
+
* I don't like how you have to manually handle after_fork for Redis, Memcached, etc.
|
36
|
+
* I don't know why Memcached::Rails isn't implemented as an ActiveRecord::Cache::Store (Dalli did it just fine!)
|
37
|
+
* Why are you asking me about :raw or whatever? Just marshal it
|
38
|
+
|
39
|
+
## How you might use it
|
40
|
+
|
41
|
+
<table>
|
42
|
+
<tr>
|
43
|
+
<th> </th>
|
44
|
+
<th><a href="https://github.com/fauna/memcached">Super-fast memcached</a></th>
|
45
|
+
<th><a href="https://github.com/mperham/dalli">Pure Ruby memcached</a> (works on <a href="http://devcenter.heroku.com/articles/memcache">Heroku</a>)</th>
|
46
|
+
<th><a href="https://redistogo.com/">Redis</a></th>
|
47
|
+
</tr>
|
48
|
+
<tr>
|
49
|
+
<td><a href="http://guides.rubyonrails.org/caching_with_rails.html#cache-stores">Rails</a></td>
|
50
|
+
<td><pre>config.cache_store = Cache.wrap(Memcached.new)</pre></td>
|
51
|
+
<td><pre>config.cache_store = Cache.wrap(Dalli::Client.new)</pre></td>
|
52
|
+
<td><pre>config.cache_store = Cache.wrap(Redis.new)</pre></td>
|
53
|
+
</tr>
|
54
|
+
<tr>
|
55
|
+
<td><a href="https://github.com/seamusabshere/cache_method">CacheMethod</a> (already uses Cache internally)</td>
|
56
|
+
<td><pre>CacheMethod.config.storage = Memcached.new</pre></td>
|
57
|
+
<td><pre>CacheMethod.config.storage = Dalli::Client.new</pre></td>
|
58
|
+
<td><pre>CacheMethod.config.storage = Redis.new</pre></td>
|
59
|
+
</tr>
|
60
|
+
<tr>
|
61
|
+
<td><a href="https://github.com/seamusabshere/lock_method">LockMethod</a> (already uses Cache internally)</td>
|
62
|
+
<td><pre>LockMethod.config.storage = Memcached.new</pre></td>
|
63
|
+
<td><pre>LockMethod.config.storage = Dalli::Client.new</pre></td>
|
64
|
+
<td><pre>LockMethod.config.storage = Redis.new</pre></td>
|
65
|
+
</tr>
|
66
|
+
<tr>
|
67
|
+
<td>Your own library</td>
|
68
|
+
<td>
|
69
|
+
<pre>
|
70
|
+
# Accept any client, let Cache take care of it
|
71
|
+
def cache=(raw_client)
|
72
|
+
@cache = Cache.wrap(raw_client)
|
73
|
+
end
|
74
|
+
</pre>
|
75
|
+
</td>
|
76
|
+
<td>
|
77
|
+
<pre>
|
78
|
+
# Accept any client, let Cache take care of it
|
79
|
+
def cache=(raw_client)
|
80
|
+
@cache = Cache.wrap(raw_client)
|
81
|
+
end
|
82
|
+
</pre>
|
83
|
+
</td>
|
84
|
+
<td>
|
85
|
+
<pre>
|
86
|
+
# Accept any client, let Cache take care of it
|
87
|
+
def cache=(raw_client)
|
88
|
+
@cache = Cache.wrap(raw_client)
|
89
|
+
end
|
90
|
+
</pre>
|
91
|
+
</td>
|
92
|
+
</tr>
|
93
|
+
</table>
|
94
|
+
|
95
|
+
## Forking/threading
|
96
|
+
|
97
|
+
When you use a Cache object to wrap Memcached or Redis, you don't have to worry about forking or threading.
|
98
|
+
|
99
|
+
For example, you don't have to set up unicorn or PhusionPassenger's <tt>after_fork</tt>.
|
100
|
+
|
101
|
+
## TTL
|
102
|
+
|
103
|
+
0 means don't expire.
|
104
|
+
|
105
|
+
## Other examples
|
106
|
+
|
107
|
+
It defaults to an in-process memory store:
|
108
|
+
|
109
|
+
@cache = Cache.new
|
110
|
+
@cache.set 'hello'
|
111
|
+
@cache.get 'hello', 'world'
|
112
|
+
|
113
|
+
You can specify a more useful cache client:
|
114
|
+
|
115
|
+
require 'memcached' # a really fast memcached client gem
|
116
|
+
require 'cache' # this gem, which provides a standard interface
|
117
|
+
raw_client = Memcached.new '127.0.0.1:11211'
|
118
|
+
@cache = Cache.wrap(raw_client)
|
119
|
+
|
120
|
+
or
|
121
|
+
|
122
|
+
require 'redis' # the redis key-value store
|
123
|
+
require 'cache' # this gem, which provides a standard interface
|
124
|
+
raw_client = Redis.new
|
125
|
+
@cache = Cache.wrap(raw_client)
|
126
|
+
|
127
|
+
or
|
128
|
+
|
129
|
+
require 'dalli' # the dalli memcached client used by heroku
|
130
|
+
require 'cache' # this gem, which provides a standard interface
|
131
|
+
raw_client = Dalli::Client.new
|
132
|
+
@cache = Cache.wrap(raw_client)
|
133
|
+
|
134
|
+
Don't know why you would ever want to do this:
|
135
|
+
|
136
|
+
# Piggyback off the default rails cache
|
137
|
+
@cache = Cache.wrap(Rails.cache)
|
138
|
+
|
139
|
+
## Supported clients
|
140
|
+
|
141
|
+
Supported memcached clients:
|
142
|
+
|
143
|
+
* [memcached](https://github.com/fauna/memcached) (super fast!)
|
144
|
+
* [dalli](https://github.com/mperham/dalli) (pure ruby, recommended if you're on heroku)
|
145
|
+
* [memcache-client](https://github.com/mperham/memcache-client) (not recommended. the one that comes with Rails.)
|
146
|
+
|
147
|
+
Supported Redis clients:
|
148
|
+
|
149
|
+
* [redis](https://github.com/ezmobius/redis-rb)
|
150
|
+
|
151
|
+
## Copyright
|
152
|
+
|
153
|
+
Copyright 2011 Seamus Abshere
|
data/Rakefile
CHANGED
@@ -11,12 +11,31 @@ end
|
|
11
11
|
|
12
12
|
task :default => :test
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
# http://stackoverflow.com/questions/2138427/can-i-get-my-readme-textile-into-my-rdoc-with-proper-formatting
|
15
|
+
desc "Generate RDoc"
|
16
|
+
task :rdoc => ['doc:generate']
|
17
|
+
namespace :doc do
|
18
|
+
project_root = File.expand_path(File.dirname(__FILE__))
|
19
|
+
doc_destination = File.join(project_root, 'rdoc')
|
20
|
+
files = Dir.glob(File.join(project_root, 'lib', '**', '*.rb')) + [ File.join(project_root, 'README.md') ]
|
21
|
+
begin
|
22
|
+
require 'yard'
|
23
|
+
require 'yard/rake/yardoc_task'
|
24
|
+
|
25
|
+
YARD::Rake::YardocTask.new(:generate) do |yt|
|
26
|
+
yt.files = files
|
27
|
+
yt.options = ['--output-dir', doc_destination, '--readme', 'README.md']
|
28
|
+
end
|
29
|
+
rescue LoadError
|
30
|
+
desc "Generate YARD Documentation"
|
31
|
+
task :generate do
|
32
|
+
abort "Please install the YARD gem to generate rdoc."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Remove generated documenation"
|
37
|
+
task :clean do
|
38
|
+
rm_r doc_dir if File.exists?(doc_destination)
|
39
|
+
end
|
17
40
|
|
18
|
-
rdoc.rdoc_dir = 'rdoc'
|
19
|
-
rdoc.title = "cache #{version}"
|
20
|
-
rdoc.rdoc_files.include('README*')
|
21
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
41
|
end
|
data/cache.gemspec
CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
|
22
22
|
s.add_dependency 'activesupport', '>=2.3.11' # for default memory store
|
23
23
|
s.add_dependency 'i18n' # activesupport
|
24
|
+
s.add_development_dependency 'yard'
|
24
25
|
s.add_development_dependency 'test-unit'
|
25
26
|
s.add_development_dependency 'redis'
|
26
27
|
s.add_development_dependency 'dalli'
|
data/lib/cache.rb
CHANGED
@@ -3,9 +3,9 @@ class Cache
|
|
3
3
|
autoload :Config, 'cache/config'
|
4
4
|
autoload :Storage, 'cache/storage'
|
5
5
|
|
6
|
-
# Create a new Cache
|
6
|
+
# Create a new Cache instance by wrapping a client of your choice.
|
7
7
|
#
|
8
|
-
# Defaults to an in-process memory store
|
8
|
+
# Defaults to an in-process memory store.
|
9
9
|
#
|
10
10
|
# Supported memcached clients:
|
11
11
|
# * memcached[https://github.com/fauna/memcached] (either a Memcached or a Memcached::Rails)
|
@@ -17,8 +17,12 @@ class Cache
|
|
17
17
|
#
|
18
18
|
# Example:
|
19
19
|
# raw_client = Memcached.new('127.0.0.1:11211')
|
20
|
-
# cache = Cache.
|
21
|
-
def
|
20
|
+
# cache = Cache.wrap raw_client
|
21
|
+
def self.wrap(client = nil)
|
22
|
+
new client
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(client = nil) #:nodoc:
|
22
26
|
config.client = client
|
23
27
|
end
|
24
28
|
|
@@ -34,7 +38,7 @@ class Cache
|
|
34
38
|
#
|
35
39
|
# Example:
|
36
40
|
# cache.get 'hello'
|
37
|
-
def get(k)
|
41
|
+
def get(k, ignored_options = nil)
|
38
42
|
storage.get k
|
39
43
|
end
|
40
44
|
|
@@ -43,7 +47,7 @@ class Cache
|
|
43
47
|
# Example:
|
44
48
|
# cache.set 'hello', 'world'
|
45
49
|
# cache.set 'hello', 'world', 80 # seconds til it expires
|
46
|
-
def set(k, v, ttl = nil)
|
50
|
+
def set(k, v, ttl = nil, ignored_options = nil)
|
47
51
|
storage.set k, v, ttl
|
48
52
|
end
|
49
53
|
|
@@ -51,7 +55,7 @@ class Cache
|
|
51
55
|
#
|
52
56
|
# Example:
|
53
57
|
# cache.delete 'hello'
|
54
|
-
def delete(k)
|
58
|
+
def delete(k, ignored_options = nil)
|
55
59
|
storage.delete k
|
56
60
|
end
|
57
61
|
|
@@ -62,4 +66,73 @@ class Cache
|
|
62
66
|
def flush
|
63
67
|
storage.flush
|
64
68
|
end
|
69
|
+
|
70
|
+
alias :clear :flush
|
71
|
+
|
72
|
+
# Check if something exists.
|
73
|
+
#
|
74
|
+
# Example:
|
75
|
+
# cache.exist? 'hello'
|
76
|
+
def exist?(k, ignored_options = nil)
|
77
|
+
storage.exist? k
|
78
|
+
end
|
79
|
+
|
80
|
+
# Increment a value.
|
81
|
+
#
|
82
|
+
# Example:
|
83
|
+
# cache.increment 'high-fives'
|
84
|
+
def increment(k, amount = 1, ignored_options = nil)
|
85
|
+
storage.increment k, amount
|
86
|
+
end
|
87
|
+
|
88
|
+
# Decrement a value.
|
89
|
+
#
|
90
|
+
# Example:
|
91
|
+
# cache.decrement 'high-fives'
|
92
|
+
def decrement(k, amount = 1, ignored_options = nil)
|
93
|
+
storage.decrement k, amount
|
94
|
+
end
|
95
|
+
|
96
|
+
# Reset the cache connection. You shouldn't really use this, because it happens automatically on forking/threading.
|
97
|
+
def reset
|
98
|
+
storage.reset
|
99
|
+
end
|
100
|
+
|
101
|
+
# Try to get a value and if it doesn't exist, set it to the result of the block.
|
102
|
+
#
|
103
|
+
# Example:
|
104
|
+
# cache.fetch 'hello' { 'world' }
|
105
|
+
def fetch(k, options_ignored_except_expires_in = {}, &blk)
|
106
|
+
storage.fetch k, options_ignored_except_expires_in[:expires_in], &blk
|
107
|
+
end
|
108
|
+
|
109
|
+
# Get the current value (if any), pass it into a block, and set the result.
|
110
|
+
#
|
111
|
+
# Example:
|
112
|
+
# cache.cas 'hello' { |current| 'world' }
|
113
|
+
def cas(k, ttl = nil, &blk)
|
114
|
+
storage.cas k, ttl, &blk
|
115
|
+
end
|
116
|
+
|
117
|
+
alias :compare_and_swap :cas
|
118
|
+
|
119
|
+
# Get stats.
|
120
|
+
#
|
121
|
+
# Example:
|
122
|
+
# cache.stats
|
123
|
+
def stats
|
124
|
+
storage.stats
|
125
|
+
end
|
126
|
+
|
127
|
+
def write(k, v, options_ignored_except_expires_in = {}) #:nodoc:
|
128
|
+
storage.set k, v, options_ignored_except_expires_in[:expires_in]
|
129
|
+
end
|
130
|
+
|
131
|
+
def read(k, ignored_options = nil) #:nodoc:
|
132
|
+
storage.get k
|
133
|
+
end
|
134
|
+
|
135
|
+
def read_multi(*ks) #:nodoc:
|
136
|
+
ks.map { |k| storage.get k }
|
137
|
+
end
|
65
138
|
end
|
data/lib/cache/storage.rb
CHANGED
@@ -12,17 +12,17 @@ class Cache
|
|
12
12
|
def get(k)
|
13
13
|
reset_if_forked_or_threaded
|
14
14
|
if memcached?
|
15
|
-
begin;
|
15
|
+
begin; bare.get(k); rescue ::Memcached::NotFound; nil; end
|
16
16
|
elsif dalli? or memcached_rails? or mem_cache?
|
17
|
-
|
17
|
+
bare.get k
|
18
18
|
elsif redis?
|
19
|
-
if cached_v =
|
19
|
+
if cached_v = bare.get(k) and cached_v.is_a?(::String)
|
20
20
|
::Marshal.load cached_v
|
21
21
|
end
|
22
22
|
elsif active_support_store?
|
23
|
-
|
23
|
+
bare.read k
|
24
24
|
else
|
25
|
-
raise "Don't know how to GET with #{
|
25
|
+
raise "Don't know how to GET with #{bare.inspect}"
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -31,51 +31,130 @@ class Cache
|
|
31
31
|
ttl = ttl.to_i
|
32
32
|
reset_if_forked_or_threaded
|
33
33
|
if memcached? or dalli? or memcached_rails? or mem_cache?
|
34
|
-
|
34
|
+
bare.set k, v, ttl
|
35
35
|
elsif redis?
|
36
36
|
if ttl == 0
|
37
|
-
|
37
|
+
bare.set k, ::Marshal.dump(v)
|
38
38
|
else
|
39
|
-
|
39
|
+
bare.setex k, ttl, ::Marshal.dump(v)
|
40
40
|
end
|
41
41
|
elsif active_support_store?
|
42
42
|
if ttl == 0
|
43
|
-
|
43
|
+
bare.write k, v # never expire
|
44
44
|
else
|
45
|
-
|
45
|
+
bare.write k, v, :expires_in => ttl
|
46
46
|
end
|
47
47
|
else
|
48
|
-
raise "Don't know how to SET with #{
|
48
|
+
raise "Don't know how to SET with #{bare.inspect}"
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
52
|
def delete(k)
|
53
53
|
reset_if_forked_or_threaded
|
54
54
|
if memcached?
|
55
|
-
begin;
|
55
|
+
begin; bare.delete(k); rescue ::Memcached::NotFound; nil; end
|
56
56
|
elsif redis?
|
57
|
-
|
57
|
+
bare.del k
|
58
58
|
elsif dalli? or memcached_rails? or mem_cache? or active_support_store?
|
59
|
-
|
59
|
+
bare.delete k
|
60
60
|
else
|
61
|
-
raise "Don't know how to DELETE with #{
|
61
|
+
raise "Don't know how to DELETE with #{bare.inspect}"
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
65
|
def flush
|
66
66
|
reset_if_forked_or_threaded
|
67
|
-
|
67
|
+
bare.send %w{ flush flushdb flush_all clear }.detect { |flush_cmd| bare.respond_to? flush_cmd }
|
68
|
+
end
|
69
|
+
|
70
|
+
# TODO detect nils
|
71
|
+
def exist?(k)
|
72
|
+
reset_if_forked_or_threaded
|
73
|
+
if memcached?
|
74
|
+
begin; bare.get(k); true; rescue ::Memcached::NotFound; false; end
|
75
|
+
elsif redis?
|
76
|
+
bare.exists k
|
77
|
+
elsif bare.respond_to?(:exist?)
|
78
|
+
# slow because we're looking it up
|
79
|
+
bare.exist? k
|
80
|
+
else
|
81
|
+
# weak because it doesn't detect keys that equal nil
|
82
|
+
!get(k).nil?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# TODO use native memcached increment if available
|
87
|
+
# TODO don't reset the timer!
|
88
|
+
def increment(k, amount)
|
89
|
+
# reset_if_forked_or_threaded - uses get
|
90
|
+
new_v = get(k).to_i + amount
|
91
|
+
set k, new_v, 0
|
92
|
+
new_v
|
93
|
+
end
|
94
|
+
|
95
|
+
def decrement(k, amount)
|
96
|
+
# reset_if_forked_or_threaded - uses increment, which uses get
|
97
|
+
increment k, -amount
|
98
|
+
end
|
99
|
+
|
100
|
+
# TODO don't resort to trickery like this
|
101
|
+
def reset
|
102
|
+
@pid = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
def fetch(k, ttl, &blk)
|
106
|
+
ttl ||= parent.config.default_ttl
|
107
|
+
ttl = ttl.to_i
|
108
|
+
reset_if_forked_or_threaded
|
109
|
+
if dalli? or mem_cache?
|
110
|
+
bare.fetch k, ttl, &blk
|
111
|
+
elsif active_support_store?
|
112
|
+
bare.fetch k, { :expires_in => ttl }, &blk
|
113
|
+
else
|
114
|
+
if exist? k
|
115
|
+
get k
|
116
|
+
elsif blk
|
117
|
+
v = blk.call
|
118
|
+
set k, v, ttl
|
119
|
+
v
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def cas(k, ttl, &blk)
|
125
|
+
ttl ||= parent.config.default_ttl
|
126
|
+
ttl = ttl.to_i
|
127
|
+
reset_if_forked_or_threaded
|
128
|
+
if memcached?
|
129
|
+
begin; bare.cas(k, ttl, &blk); rescue ::Memcached::NotFound; nil; end
|
130
|
+
elsif dalli? or memcached_rails?
|
131
|
+
bare.cas k, ttl, &blk
|
132
|
+
elsif blk and exist?(k)
|
133
|
+
old_v = get k
|
134
|
+
new_v = blk.call old_v
|
135
|
+
set k, new_v, ttl
|
136
|
+
new_v
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def stats
|
141
|
+
reset_if_forked_or_threaded
|
142
|
+
if bare.respond_to?(:stats)
|
143
|
+
bare.stats
|
144
|
+
else
|
145
|
+
{}
|
146
|
+
end
|
68
147
|
end
|
69
148
|
|
70
149
|
private
|
71
150
|
|
72
|
-
def
|
73
|
-
@
|
151
|
+
def bare
|
152
|
+
@bare ||= parent.config.client
|
74
153
|
end
|
75
154
|
|
76
155
|
def reset_if_forked_or_threaded
|
77
156
|
if fork_detected?
|
78
|
-
$stderr.puts "fork detected" if ENV['CACHE_DEBUG'] == 'true'
|
157
|
+
# $stderr.puts "fork detected" if ENV['CACHE_DEBUG'] == 'true'
|
79
158
|
if dalli?
|
80
159
|
parent.config.client.close
|
81
160
|
elsif dalli_store?
|
@@ -83,18 +162,18 @@ class Cache
|
|
83
162
|
elsif memcached? or memcached_rails?
|
84
163
|
cloned_client = parent.config.client.clone
|
85
164
|
parent.config.client = cloned_client
|
86
|
-
@
|
165
|
+
@bare = parent.config.client
|
87
166
|
elsif redis?
|
88
167
|
parent.config.client.client.connect
|
89
168
|
elsif mem_cache?
|
90
169
|
parent.config.client.reset
|
91
170
|
end
|
92
171
|
elsif new_thread_detected?
|
93
|
-
$stderr.puts "new thread detected" if ENV['CACHE_DEBUG'] == 'true'
|
172
|
+
# $stderr.puts "new thread detected" if ENV['CACHE_DEBUG'] == 'true'
|
94
173
|
if memcached? or memcached_rails?
|
95
174
|
cloned_client = parent.config.client.clone
|
96
175
|
parent.config.client = cloned_client
|
97
|
-
@
|
176
|
+
@bare = parent.config.client
|
98
177
|
end
|
99
178
|
end
|
100
179
|
end
|
@@ -113,56 +192,56 @@ class Cache
|
|
113
192
|
|
114
193
|
def dalli?
|
115
194
|
return @dalli_query[0] if @dalli_query.is_a?(::Array)
|
116
|
-
answer = (defined?(::Dalli) and
|
195
|
+
answer = (defined?(::Dalli) and bare.is_a?(::Dalli::Client))
|
117
196
|
@dalli_query = [answer]
|
118
197
|
answer
|
119
198
|
end
|
120
199
|
|
121
200
|
def active_support_store?
|
122
201
|
return @active_support_store_query[0] if @active_support_store_query.is_a?(::Array)
|
123
|
-
answer = (defined?(::ActiveSupport::Cache) and
|
202
|
+
answer = (defined?(::ActiveSupport::Cache) and bare.is_a?(::ActiveSupport::Cache::Store))
|
124
203
|
@active_support_store_query = [answer]
|
125
204
|
answer
|
126
205
|
end
|
127
206
|
|
128
207
|
def dalli_store?
|
129
208
|
return @dalli_store_query[0] if @dalli_store_query.is_a?(::Array)
|
130
|
-
answer = (defined?(::ActiveSupport::Cache::DalliStore) and
|
209
|
+
answer = (defined?(::ActiveSupport::Cache::DalliStore) and bare.is_a?(::ActiveSupport::Cache::DalliStore))
|
131
210
|
@dalli_store_query = [answer]
|
132
211
|
answer
|
133
212
|
end
|
134
213
|
|
135
214
|
def memory_store?
|
136
215
|
return @memory_store_query[0] if @memory_store_query.is_a?(::Array)
|
137
|
-
answer = (defined?(::ActiveSupport::Cache::MemoryStore) and
|
216
|
+
answer = (defined?(::ActiveSupport::Cache::MemoryStore) and bare.is_a?(::ActiveSupport::Cache::MemoryStore))
|
138
217
|
@memory_store_query = [answer]
|
139
218
|
answer
|
140
219
|
end
|
141
220
|
|
142
221
|
def mem_cache?
|
143
222
|
return @mem_cache_query[0] if @mem_cache_query.is_a?(::Array)
|
144
|
-
answer = (defined?(::MemCache) and
|
223
|
+
answer = (defined?(::MemCache) and bare.is_a?(::MemCache))
|
145
224
|
@mem_cache_query = [answer]
|
146
225
|
answer
|
147
226
|
end
|
148
227
|
|
149
228
|
def memcached?
|
150
229
|
return @memcached_query[0] if @memcached_query.is_a?(::Array)
|
151
|
-
answer = (defined?(::Memcached) and
|
230
|
+
answer = (defined?(::Memcached) and bare.is_a?(::Memcached) and not bare.is_a?(::Memcached::Rails))
|
152
231
|
@memcached_query = [answer]
|
153
232
|
answer
|
154
233
|
end
|
155
234
|
|
156
235
|
def memcached_rails?
|
157
236
|
return @memcached_rails_query[0] if @memcached_rails_query.is_a?(::Array)
|
158
|
-
answer = (defined?(::Memcached) and
|
237
|
+
answer = (defined?(::Memcached) and bare.is_a?(::Memcached::Rails))
|
159
238
|
@memcached_rails_query = [answer]
|
160
239
|
answer
|
161
240
|
end
|
162
241
|
|
163
242
|
def redis?
|
164
243
|
return @redis_query[0] if @redis_query.is_a?(::Array)
|
165
|
-
answer = (defined?(::Redis) and
|
244
|
+
answer = (defined?(::Redis) and bare.is_a?(::Redis))
|
166
245
|
@redis_query = [answer]
|
167
246
|
answer
|
168
247
|
end
|
data/lib/cache/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/profile/benchmark.rb
CHANGED
@@ -53,14 +53,14 @@ class Dalli::ClientCompat < Dalli::Client
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
class Cache::Compat < Cache
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
56
|
+
# class Cache::Compat < Cache
|
57
|
+
# def set(*args)
|
58
|
+
# super(*args[0..2])
|
59
|
+
# end
|
60
|
+
# def get(*args)
|
61
|
+
# super(args.first)
|
62
|
+
# end
|
63
|
+
# end
|
64
64
|
|
65
65
|
class Bench
|
66
66
|
|
@@ -108,8 +108,8 @@ class Bench
|
|
108
108
|
|
109
109
|
def reset_clients
|
110
110
|
@clients = {
|
111
|
-
"cache:libm:bin" => Cache
|
112
|
-
"cache:dalli:bin" => Cache
|
111
|
+
"cache:libm:bin" => Cache.wrap(Memcached.new(['127.0.0.1:43042', '127.0.0.1:43043'], :buffer_requests => false, :no_block => false, :namespace => "namespace", :binary_protocol => true)),
|
112
|
+
"cache:dalli:bin" => Cache.wrap(Dalli::Client.new(['127.0.0.1:43042', '127.0.0.1:43043'], :marshal => false, :threadsafe => false)),
|
113
113
|
"libm:ascii" => Memcached::Rails.new(
|
114
114
|
['127.0.0.1:43042', '127.0.0.1:43043'],
|
115
115
|
:buffer_requests => false, :no_block => false, :namespace => "namespace"),
|
data/test/shared_tests.rb
CHANGED
@@ -34,8 +34,97 @@ module SharedTests
|
|
34
34
|
|
35
35
|
def test_flush
|
36
36
|
@cache.set 'hello', 'world'
|
37
|
-
|
37
|
+
assert @cache.exist?('hello')
|
38
38
|
@cache.flush
|
39
|
+
assert !@cache.exist?('hello')
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_exist
|
43
|
+
assert !@cache.exist?('hello')
|
44
|
+
@cache.set 'hello', 'world'
|
45
|
+
assert @cache.exist?('hello')
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_exist_key_with_nil_value
|
49
|
+
assert !@cache.exist?('hello')
|
50
|
+
@cache.set 'hello', nil
|
51
|
+
assert @cache.exist?('hello')
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_stats
|
55
|
+
assert_nothing_raised do
|
56
|
+
@cache.stats
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_reset
|
61
|
+
@cache.set 'hello', 'world'
|
62
|
+
assert @cache.exist?('hello')
|
63
|
+
@cache.reset
|
64
|
+
# still there!
|
65
|
+
assert @cache.exist?('hello')
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_fetch
|
69
|
+
assert_equal nil, @cache.fetch('hello')
|
70
|
+
assert_equal 'world', @cache.fetch('hello') { 'world' }
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_cas
|
74
|
+
toggle = lambda do |current|
|
75
|
+
current == 'on' ? 'off' : 'on'
|
76
|
+
end
|
77
|
+
|
78
|
+
@cache.set 'lights', 'on'
|
79
|
+
assert_equal 'on', @cache.get('lights')
|
80
|
+
@cache.cas 'lights', &toggle
|
81
|
+
assert_equal 'off', @cache.get('lights')
|
82
|
+
@cache.cas 'lights', &toggle
|
83
|
+
assert_equal 'on', @cache.get('lights')
|
84
|
+
@cache.cas 'lights', &toggle
|
85
|
+
assert_equal 'off', @cache.get('lights')
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_write
|
89
|
+
@cache.write 'hello', 'world'
|
90
|
+
assert_equal 'world', @cache.get('hello')
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_write_with_expires_in
|
94
|
+
@cache.write 'hello', 'world', :expires_in => 1
|
95
|
+
assert_equal 'world', @cache.get('hello')
|
96
|
+
sleep 2
|
39
97
|
assert_equal nil, @cache.get('hello')
|
40
98
|
end
|
99
|
+
|
100
|
+
def test_read
|
101
|
+
@cache.set 'hello', 'world'
|
102
|
+
assert_equal 'world', @cache.read('hello')
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_increment
|
106
|
+
assert !@cache.exist?('high-fives')
|
107
|
+
@cache.increment 'high-fives'
|
108
|
+
assert_equal 1, @cache.get('high-fives')
|
109
|
+
@cache.increment 'high-fives'
|
110
|
+
assert_equal 2, @cache.get('high-fives')
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_decrement
|
114
|
+
assert !@cache.exist?('high-fives')
|
115
|
+
@cache.decrement 'high-fives'
|
116
|
+
assert_equal -1, @cache.get('high-fives')
|
117
|
+
@cache.decrement 'high-fives'
|
118
|
+
assert_equal -2, @cache.get('high-fives')
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_read_multi
|
122
|
+
@cache.set 'hello', 'world'
|
123
|
+
@cache.set 'privyet', 'mir'
|
124
|
+
assert_equal %w{world mir}, @cache.read_multi('hello', 'privyet')
|
125
|
+
end
|
126
|
+
|
127
|
+
alias :test_clear :test_flush
|
128
|
+
|
129
|
+
alias :test_compare_and_swap :test_cas
|
41
130
|
end
|
@@ -4,13 +4,13 @@ require 'memcached'
|
|
4
4
|
|
5
5
|
class TestMemcachedRailsStorage < Test::Unit::TestCase
|
6
6
|
def raw_client
|
7
|
-
Memcached::Rails.new 'localhost:11211'
|
7
|
+
Memcached::Rails.new 'localhost:11211', :support_cas => true
|
8
8
|
end
|
9
9
|
|
10
10
|
include SharedTests
|
11
11
|
|
12
|
-
def
|
13
|
-
@cache.storage.send(:
|
12
|
+
def get_bare_id
|
13
|
+
@cache.storage.send(:bare).object_id
|
14
14
|
end
|
15
15
|
|
16
16
|
def test_treats_as_not_thread_safe
|
@@ -18,17 +18,17 @@ class TestMemcachedRailsStorage < Test::Unit::TestCase
|
|
18
18
|
@cache.get 'hi'
|
19
19
|
|
20
20
|
# get the main thread's bare client
|
21
|
-
|
21
|
+
main_thread_bare_id = get_bare_id
|
22
22
|
|
23
23
|
# sanity check that it's not changing every time
|
24
24
|
@cache.get 'hi'
|
25
|
-
assert_equal
|
25
|
+
assert_equal main_thread_bare_id, get_bare_id
|
26
26
|
|
27
27
|
# create a new thread and get its bare client
|
28
|
-
|
28
|
+
new_thread_bare_id = Thread.new { @cache.get 'hi'; get_bare_id }.value
|
29
29
|
|
30
30
|
# make sure the bare client was reinitialized
|
31
|
-
assert(
|
31
|
+
assert(main_thread_bare_id != new_thread_bare_id)
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_treats_as_not_fork_safe
|
@@ -36,16 +36,16 @@ class TestMemcachedRailsStorage < Test::Unit::TestCase
|
|
36
36
|
@cache.get 'hi'
|
37
37
|
|
38
38
|
# get the main process's bare client
|
39
|
-
|
39
|
+
parent_process_bare_id = get_bare_id
|
40
40
|
|
41
41
|
# sanity check that it's not changing every time
|
42
42
|
@cache.get 'hi'
|
43
|
-
assert_equal
|
43
|
+
assert_equal parent_process_bare_id, get_bare_id
|
44
44
|
|
45
45
|
# fork a new process
|
46
46
|
pid = Kernel.fork do
|
47
47
|
@cache.get 'hi'
|
48
|
-
raise "Didn't split!" if
|
48
|
+
raise "Didn't split!" if parent_process_bare_id == get_bare_id
|
49
49
|
end
|
50
50
|
Process.wait pid
|
51
51
|
|
@@ -4,13 +4,13 @@ require 'memcached'
|
|
4
4
|
|
5
5
|
class TestMemcachedStorage < Test::Unit::TestCase
|
6
6
|
def raw_client
|
7
|
-
Memcached.new 'localhost:11211'
|
7
|
+
Memcached.new 'localhost:11211', :support_cas => true
|
8
8
|
end
|
9
9
|
|
10
10
|
include SharedTests
|
11
11
|
|
12
|
-
def
|
13
|
-
@cache.storage.send(:
|
12
|
+
def get_bare_id
|
13
|
+
@cache.storage.send(:bare).object_id
|
14
14
|
end
|
15
15
|
|
16
16
|
def test_treats_as_not_thread_safe
|
@@ -18,17 +18,17 @@ class TestMemcachedStorage < Test::Unit::TestCase
|
|
18
18
|
@cache.get 'hi'
|
19
19
|
|
20
20
|
# get the main thread's bare client
|
21
|
-
|
21
|
+
main_thread_bare_id = get_bare_id
|
22
22
|
|
23
23
|
# sanity check that it's not changing every time
|
24
24
|
@cache.get 'hi'
|
25
|
-
assert_equal
|
25
|
+
assert_equal main_thread_bare_id, get_bare_id
|
26
26
|
|
27
27
|
# create a new thread and get its bare client
|
28
|
-
|
28
|
+
new_thread_bare_id = Thread.new { @cache.get 'hi'; get_bare_id }.value
|
29
29
|
|
30
30
|
# make sure the bare client was reinitialized
|
31
|
-
assert(
|
31
|
+
assert(main_thread_bare_id != new_thread_bare_id)
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_treats_as_not_fork_safe
|
@@ -36,16 +36,16 @@ class TestMemcachedStorage < Test::Unit::TestCase
|
|
36
36
|
@cache.get 'hi'
|
37
37
|
|
38
38
|
# get the main process's bare client
|
39
|
-
|
39
|
+
parent_process_bare_id = get_bare_id
|
40
40
|
|
41
41
|
# sanity check that it's not changing every time
|
42
42
|
@cache.get 'hi'
|
43
|
-
assert_equal
|
43
|
+
assert_equal parent_process_bare_id, get_bare_id
|
44
44
|
|
45
45
|
# fork a new process
|
46
46
|
pid = Kernel.fork do
|
47
47
|
@cache.get 'hi'
|
48
|
-
raise "Didn't split!" if
|
48
|
+
raise "Didn't split!" if parent_process_bare_id == get_bare_id
|
49
49
|
end
|
50
50
|
Process.wait pid
|
51
51
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Seamus Abshere
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-03-
|
18
|
+
date: 2011-03-04 00:00:00 -06:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -49,7 +49,7 @@ dependencies:
|
|
49
49
|
type: :runtime
|
50
50
|
version_requirements: *id002
|
51
51
|
- !ruby/object:Gem::Dependency
|
52
|
-
name:
|
52
|
+
name: yard
|
53
53
|
prerelease: false
|
54
54
|
requirement: &id003 !ruby/object:Gem::Requirement
|
55
55
|
none: false
|
@@ -63,7 +63,7 @@ dependencies:
|
|
63
63
|
type: :development
|
64
64
|
version_requirements: *id003
|
65
65
|
- !ruby/object:Gem::Dependency
|
66
|
-
name:
|
66
|
+
name: test-unit
|
67
67
|
prerelease: false
|
68
68
|
requirement: &id004 !ruby/object:Gem::Requirement
|
69
69
|
none: false
|
@@ -77,7 +77,7 @@ dependencies:
|
|
77
77
|
type: :development
|
78
78
|
version_requirements: *id004
|
79
79
|
- !ruby/object:Gem::Dependency
|
80
|
-
name:
|
80
|
+
name: redis
|
81
81
|
prerelease: false
|
82
82
|
requirement: &id005 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
@@ -91,7 +91,7 @@ dependencies:
|
|
91
91
|
type: :development
|
92
92
|
version_requirements: *id005
|
93
93
|
- !ruby/object:Gem::Dependency
|
94
|
-
name:
|
94
|
+
name: dalli
|
95
95
|
prerelease: false
|
96
96
|
requirement: &id006 !ruby/object:Gem::Requirement
|
97
97
|
none: false
|
@@ -105,7 +105,7 @@ dependencies:
|
|
105
105
|
type: :development
|
106
106
|
version_requirements: *id006
|
107
107
|
- !ruby/object:Gem::Dependency
|
108
|
-
name:
|
108
|
+
name: memcached
|
109
109
|
prerelease: false
|
110
110
|
requirement: &id007 !ruby/object:Gem::Requirement
|
111
111
|
none: false
|
@@ -118,6 +118,20 @@ dependencies:
|
|
118
118
|
version: "0"
|
119
119
|
type: :development
|
120
120
|
version_requirements: *id007
|
121
|
+
- !ruby/object:Gem::Dependency
|
122
|
+
name: memcache-client
|
123
|
+
prerelease: false
|
124
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
type: :development
|
134
|
+
version_requirements: *id008
|
121
135
|
description: A unified cache handling interface for Ruby, inspired by (but simpler than) Perl's Cache::Cache
|
122
136
|
email:
|
123
137
|
- seamus@abshere.net
|
@@ -131,7 +145,7 @@ files:
|
|
131
145
|
- .gitignore
|
132
146
|
- BENCHMARKS
|
133
147
|
- Gemfile
|
134
|
-
- README.
|
148
|
+
- README.md
|
135
149
|
- Rakefile
|
136
150
|
- cache.gemspec
|
137
151
|
- lib/cache.rb
|
data/README.rdoc
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
= cache
|
2
|
-
|
3
|
-
A unified cache handling interface, inspired (but simpler than) Perl's Cache::Cache[http://cpan.uwinnipeg.ca/module/Cache::Cache] and CHI[http://cpan.uwinnipeg.ca/module/CHI].
|
4
|
-
|
5
|
-
Takes care of exceptions like Memcached::NotFound and also forking/threading.
|
6
|
-
|
7
|
-
== Example
|
8
|
-
|
9
|
-
It uses a (useless?) memory store by default:
|
10
|
-
|
11
|
-
cache = Cache.new
|
12
|
-
cache.set 'hello'
|
13
|
-
cache.get 'hello', 'world'
|
14
|
-
|
15
|
-
You can specify a more useful cache client:
|
16
|
-
|
17
|
-
require 'memcached' # a really fast memcached client gem
|
18
|
-
require 'cache' # this gem, which provides a standard interface
|
19
|
-
raw_client = Memcached.new '127.0.0.1:11211'
|
20
|
-
cache = Cache.new raw_client
|
21
|
-
|
22
|
-
or
|
23
|
-
|
24
|
-
require 'redis' # the redis key-value store
|
25
|
-
require 'cache' # this gem, which provides a standard interface
|
26
|
-
raw_client = Redis.new
|
27
|
-
cache = Cache.new raw_client
|
28
|
-
|
29
|
-
or
|
30
|
-
|
31
|
-
require 'dalli' # the dalli memcached client used by heroku
|
32
|
-
require 'cache' # this gem, which provides a standard interface
|
33
|
-
raw_client = Dalli::Client.new
|
34
|
-
cache = Cache.new raw_client
|
35
|
-
|
36
|
-
Maybe this will even work:
|
37
|
-
|
38
|
-
# Piggyback off the default rails cache
|
39
|
-
cache = Cache.new Rails.cache
|
40
|
-
|
41
|
-
== Methods
|
42
|
-
|
43
|
-
cache.get 'hello'
|
44
|
-
cache.set 'hello', 'world'
|
45
|
-
cache.delete 'hello'
|
46
|
-
cache.flush
|
47
|
-
|
48
|
-
== Supported clients
|
49
|
-
|
50
|
-
Supported memcached clients:
|
51
|
-
|
52
|
-
* memcached[https://github.com/fauna/memcached] (either a Memcached or a Memcached::Rails)
|
53
|
-
* dalli[https://github.com/mperham/dalli] (either a Dalli::Client or an ActiveSupport::Cache::DalliStore)
|
54
|
-
* memcache-client[https://github.com/mperham/memcache-client] (MemCache, the one commonly used by Rails)
|
55
|
-
|
56
|
-
Supported Redis clients:
|
57
|
-
|
58
|
-
* redis[https://github.com/ezmobius/redis-rb]
|
59
|
-
|
60
|
-
== TTL
|
61
|
-
|
62
|
-
0 means don't expire.
|
63
|
-
|
64
|
-
== Forking/threading
|
65
|
-
|
66
|
-
When you use a Cache object to wrap Memcached or Redis, you don't have to worry about forking or threading.
|
67
|
-
|
68
|
-
For example, you don't have to set up unicorn or PhusionPassenger's <tt>after_fork</tt>.
|
69
|
-
|
70
|
-
== Rationale
|
71
|
-
|
72
|
-
I wanted a common interface to a bunch of great Ruby cache clients so I can develop gems (lock_method, cache_method) that accept any of them.
|
73
|
-
|
74
|
-
* I am so tired of rescuing from Memcached::NotFound
|
75
|
-
* I am so tired of forgetting whether it's :expires_in or :ttl
|
76
|
-
* I don't know why we ever started using read/write instead of get/set.
|
77
|
-
* I don't like how you have to manually handle after_fork for Redis, Memcached, etc.
|
78
|
-
|
79
|
-
== Currently unsupported
|
80
|
-
|
81
|
-
* cas
|
82
|
-
* fetch
|
83
|
-
* incr
|
84
|
-
|
85
|
-
== Copyright
|
86
|
-
|
87
|
-
Copyright 2011 Seamus Abshere
|