cache 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,7 +2,52 @@
2
2
 
3
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
4
 
5
- ## Supported methods
5
+ ## Quick example
6
+
7
+ require 'memcached' # a really fast memcached client gem
8
+ require 'cache' # this gem, which wraps the client to provide a standard interface
9
+ raw_client = Memcached.new('127.0.0.1:11211')
10
+ @cache = Cache.wrap(raw_client)
11
+
12
+ # don't worry, even though it's memcached gem, this won't raise Memcached::NotFound
13
+ @cache.get('hello')
14
+
15
+ # fetch is not provided by the memcached gem, the wrapper adds it
16
+ @cache.fetch('hello') { 'world' }
17
+
18
+ # don't worry, the wrapper will automatically clone the Memcached object after forking
19
+ Kernel.fork { @cache.get('hello') }
20
+
21
+ ## Rationale
22
+
23
+ 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.
24
+
25
+ * I'm tired of rescuing from Memcached::NotFound
26
+ * I'm tired of forgetting whether it's :expires_in or :ttl
27
+ * I don't know why we ever started using read/write instead of get/set.
28
+ * I don't like how you have to manually handle after_fork for Redis, Memcached, etc.
29
+ * I don't know why Memcached::Rails isn't implemented as an ActiveRecord::Cache::Store (Dalli did it just fine!)
30
+ * Why are you asking me about :raw or whatever? Just marshal it
31
+
32
+ ## Features
33
+
34
+ ### Forking/threading
35
+
36
+ When you use a Cache object to wrap Memcached or Redis, you don't have to worry about forking or threading.
37
+
38
+ For example, you don't have to set up unicorn or PhusionPassenger's <tt>after_fork</tt>.
39
+
40
+ ### TTL
41
+
42
+ 0 means don't expire.
43
+
44
+ The default ttl is 60 seconds.
45
+
46
+ ### Marshalling
47
+
48
+ Everything gets marshalled. No option to turn it into "raw" mode. If you need that kind of control, please submit a patch or just use one of the other gems directly.
49
+
50
+ ### Supported methods
6
51
 
7
52
  It will translate these methods to whatever Redis, Memcached, etc. client you're using:
8
53
 
@@ -16,6 +61,7 @@ It will translate these methods to whatever Redis, Memcached, etc. client you're
16
61
  @cache.cas 'hello' { |current| 'world' }
17
62
  @cache.increment 'high-fives'
18
63
  @cache.decrement 'high-fives'
64
+ @cache.get_multi 'hello', 'privyet', 'hallo'
19
65
 
20
66
  Also provided for Rails compatibility:
21
67
 
@@ -25,16 +71,17 @@ Also provided for Rails compatibility:
25
71
  @cache.compare_and_swap
26
72
  @cache.read_multi 'hello', 'privyet', 'hallo'
27
73
 
28
- ## Rationale
74
+ ### Supported clients
29
75
 
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.
76
+ Supported memcached clients:
31
77
 
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
78
+ * [memcached](https://github.com/fauna/memcached) (native C extensions, super fast!)
79
+ * [dalli](https://github.com/mperham/dalli) (pure ruby, recommended if you're on heroku)
80
+ * [memcache-client](https://github.com/mperham/memcache-client) (not recommended. the one that comes with Rails.)
81
+
82
+ Supported Redis clients:
83
+
84
+ * [redis](https://github.com/ezmobius/redis-rb)
38
85
 
39
86
  ## How you might use it
40
87
 
@@ -51,18 +98,6 @@ I wanted a common interface to a bunch of great Ruby cache clients so I can deve
51
98
  <td><pre>config.cache_store = Cache.wrap(Dalli::Client.new)</pre></td>
52
99
  <td><pre>config.cache_store = Cache.wrap(Redis.new)</pre></td>
53
100
  </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
101
  <tr>
67
102
  <td>Your own library</td>
68
103
  <td>
@@ -90,18 +125,20 @@ end
90
125
  </pre>
91
126
  </td>
92
127
  </tr>
128
+ <tr>
129
+ <td><a href="https://github.com/seamusabshere/cache_method">CacheMethod</a> (already uses Cache internally)</td>
130
+ <td><pre>CacheMethod.config.storage = Memcached.new</pre></td>
131
+ <td><pre>CacheMethod.config.storage = Dalli::Client.new</pre></td>
132
+ <td><pre>CacheMethod.config.storage = Redis.new</pre></td>
133
+ </tr>
134
+ <tr>
135
+ <td><a href="https://github.com/seamusabshere/lock_method">LockMethod</a> (already uses Cache internally)</td>
136
+ <td><pre>LockMethod.config.storage = Memcached.new</pre></td>
137
+ <td><pre>LockMethod.config.storage = Dalli::Client.new</pre></td>
138
+ <td><pre>LockMethod.config.storage = Redis.new</pre></td>
139
+ </tr>
93
140
  </table>
94
141
 
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
142
  ## Other examples
106
143
 
107
144
  It defaults to an in-process memory store:
@@ -112,13 +149,6 @@ It defaults to an in-process memory store:
112
149
 
113
150
  You can specify a more useful cache client:
114
151
 
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
152
  require 'redis' # the redis key-value store
123
153
  require 'cache' # this gem, which provides a standard interface
124
154
  raw_client = Redis.new
@@ -131,23 +161,10 @@ or
131
161
  raw_client = Dalli::Client.new
132
162
  @cache = Cache.wrap(raw_client)
133
163
 
134
- Don't know why you would ever want to do this:
164
+ Or you could piggyback off the default rails cache:
135
165
 
136
- # Piggyback off the default rails cache
137
166
  @cache = Cache.wrap(Rails.cache)
138
167
 
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
168
  ## Copyright
152
169
 
153
170
  Copyright 2011 Seamus Abshere
data/lib/cache.rb CHANGED
@@ -19,7 +19,11 @@ class Cache
19
19
  # raw_client = Memcached.new('127.0.0.1:11211')
20
20
  # cache = Cache.wrap raw_client
21
21
  def self.wrap(client = nil)
22
- new client
22
+ if client.is_a?(::Cache)
23
+ client
24
+ else
25
+ new client
26
+ end
23
27
  end
24
28
 
25
29
  def initialize(client = nil) #:nodoc:
@@ -48,7 +52,7 @@ class Cache
48
52
  # cache.set 'hello', 'world'
49
53
  # cache.set 'hello', 'world', 80 # seconds til it expires
50
54
  def set(k, v, ttl = nil, ignored_options = nil)
51
- storage.set k, v, ttl
55
+ storage.set k, v, extract_ttl(ttl)
52
56
  end
53
57
 
54
58
  # Delete a value.
@@ -100,10 +104,12 @@ class Cache
100
104
 
101
105
  # Try to get a value and if it doesn't exist, set it to the result of the block.
102
106
  #
107
+ # Accepts :expires_in for compatibility with Rails.
108
+ #
103
109
  # Example:
104
110
  # 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
111
+ def fetch(k, ttl = nil, &blk)
112
+ storage.fetch k, extract_ttl(ttl), &blk
107
113
  end
108
114
 
109
115
  # Get the current value (if any), pass it into a block, and set the result.
@@ -111,7 +117,7 @@ class Cache
111
117
  # Example:
112
118
  # cache.cas 'hello' { |current| 'world' }
113
119
  def cas(k, ttl = nil, &blk)
114
- storage.cas k, ttl, &blk
120
+ storage.cas k, extract_ttl(ttl), &blk
115
121
  end
116
122
 
117
123
  alias :compare_and_swap :cas
@@ -123,16 +129,39 @@ class Cache
123
129
  def stats
124
130
  storage.stats
125
131
  end
132
+
133
+ # Get multiple cache entries.
134
+ #
135
+ # Example:
136
+ # cache.get_multi 'hello', 'privyet'
137
+ def get_multi(*ks)
138
+ storage.get_multi ks
139
+ end
126
140
 
127
- def write(k, v, options_ignored_except_expires_in = {}) #:nodoc:
128
- storage.set k, v, options_ignored_except_expires_in[:expires_in]
141
+ # Like get, but accepts :expires_in for compatibility with Rails.
142
+ #
143
+ # In general, you should use get instead.
144
+ #
145
+ # Example:
146
+ # cache.write 'hello', 'world', :expires_in => 5.minutes
147
+ def write(k, v, ttl = nil)
148
+ storage.set k, v, extract_ttl(ttl)
129
149
  end
130
150
 
131
151
  def read(k, ignored_options = nil) #:nodoc:
132
152
  storage.get k
133
153
  end
134
154
 
135
- def read_multi(*ks) #:nodoc:
136
- ks.map { |k| storage.get k }
155
+ private
156
+
157
+ def extract_ttl(ttl)
158
+ case ttl
159
+ when ::Hash
160
+ ttl[:expires_in]
161
+ when ::NilClass
162
+ config.default_ttl
163
+ else
164
+ ttl.to_i
165
+ end
137
166
  end
138
167
  end
data/lib/cache/storage.rb CHANGED
@@ -25,10 +25,24 @@ class Cache
25
25
  raise "Don't know how to GET with #{bare.inspect}"
26
26
  end
27
27
  end
28
+
29
+ def get_multi(ks)
30
+ reset_if_forked_or_threaded
31
+ if memcached?
32
+ bare.get ks
33
+ elsif memcached_rails? or dalli? or mem_cache?
34
+ bare.get_multi ks
35
+ elsif active_support_store?
36
+ bare.read_multi *ks
37
+ else
38
+ ks.inject({}) do |memo, k|
39
+ memo[k] = get k if exist? k
40
+ memo
41
+ end
42
+ end
43
+ end
28
44
 
29
45
  def set(k, v, ttl)
30
- ttl ||= parent.config.default_ttl
31
- ttl = ttl.to_i
32
46
  reset_if_forked_or_threaded
33
47
  if memcached? or dalli? or memcached_rails? or mem_cache?
34
48
  bare.set k, v, ttl
@@ -103,8 +117,6 @@ class Cache
103
117
  end
104
118
 
105
119
  def fetch(k, ttl, &blk)
106
- ttl ||= parent.config.default_ttl
107
- ttl = ttl.to_i
108
120
  reset_if_forked_or_threaded
109
121
  if dalli? or mem_cache?
110
122
  bare.fetch k, ttl, &blk
@@ -122,8 +134,6 @@ class Cache
122
134
  end
123
135
 
124
136
  def cas(k, ttl, &blk)
125
- ttl ||= parent.config.default_ttl
126
- ttl = ttl.to_i
127
137
  reset_if_forked_or_threaded
128
138
  if memcached?
129
139
  begin; bare.cas(k, ttl, &blk); rescue ::Memcached::NotFound; nil; end
data/lib/cache/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Cache
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
data/test/shared_tests.rb CHANGED
@@ -70,6 +70,10 @@ module SharedTests
70
70
  assert_equal 'world', @cache.fetch('hello') { 'world' }
71
71
  end
72
72
 
73
+ def test_fetch_with_expires_in
74
+ assert_equal 'world', @cache.fetch('hello', :expires_in => 5) { 'world' }
75
+ end
76
+
73
77
  def test_cas
74
78
  toggle = lambda do |current|
75
79
  current == 'on' ? 'off' : 'on'
@@ -118,13 +122,9 @@ module SharedTests
118
122
  assert_equal -2, @cache.get('high-fives')
119
123
  end
120
124
 
121
- def test_read_multi
125
+ def test_get_multi
122
126
  @cache.set 'hello', 'world'
123
127
  @cache.set 'privyet', 'mir'
124
- assert_equal %w{world mir}, @cache.read_multi('hello', 'privyet')
128
+ assert_equal({ 'hello' => 'world', 'privyet' => 'mir'}, @cache.get_multi('hello', 'privyet', 'yoyoyo'))
125
129
  end
126
-
127
- alias :test_clear :test_flush
128
-
129
- alias :test_compare_and_swap :test_cas
130
130
  end
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: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 0
10
- version: 0.2.0
9
+ - 1
10
+ version: 0.2.1
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-04 00:00:00 -06:00
18
+ date: 2011-03-07 00:00:00 -06:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency