A utility method for caching the result of any block of Ruby code.
Redis is a simple and very fast key-value store, that can be used for all kinds of things. Resque, for example, is a system built on Redis for
processing background jobs or even scheduled jobs. Redis can be used for all kinds of different things, and so it has a very generalized API that doesn’t
make any assumptions about how you’re going to use it. The Redis API includes simple methods like get
and set
and expire
. And the Ruby gem for Redis
is a thin layer over the standard Redis API.
Caching expensive operations with Redis
But when most people use Redis, they tend to use it for caching values in a web application, like you would use memcached. And if you use Redis for caching, then you might find yourself writing the same sort of code structure over and over:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
It’s great that Redis#get
and Redis#set
and Redis#expire
are all so simple. But if you’re going to wrap expensive operations in Redis caching frequently, then what you really need is a Redis#cache
method.
Monkey patching to the rescue
With Ruby, you can monkey patch anything, so it’s not difficult to add a new convenience method to
the Ruby bindings for Redis. We can just open the Redis class and drop in a new method. You can simply add a file called lib/redis_cache.rb
to a Ruby project in order to add a cache
method ot the Redis API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The new Redis#cache
method accepts three things: a key
argument, an optional expire
argument, and a block of code. First, it checks Redis for a value at the given key. If one exists, then
it returns that value immediately. If one doesn’t exist, then it uses the code block to generate a value.
Then it sets the Redis key to that value. Then it sets the expiration, in seconds, on that key, if there
was an expiration argument provided.
This simple code teaches Redis to speak the language of caching, simplifying your high-level application code. Instead of the code pattern shown in the first code sample, distracting the reader from the problem at hand with caching details, the application code can be all about the values that it wants to calculate, with caching wrapped unobtrusively around the meat of the solution code.
For example, from the simple unit tests for the Redis#cache
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
If you have a do_something
method that takes a long time to complete, then you can cache that
method at the key “key” with redis.cache('key') { do_something }
. Simple.
Recalculate every time
You might want to disable caching in development and test modes. You can add support for disabling
caching by adding a second optional argument to the Redis#cache
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
If recalculate
is true, then the code block will be executed every time. So you can make
that value true in development and test modes like this:
1 2 3 4 |
|
Add timeout and default parameters
Generally when you use this kind of caching, you’re using it to cache the results of some operation that’s really slow. If that operation is really slow because it involves the network, then maybe sometimes it might time out, and you might want to specify a default value to use instead when it times out. It’s easy to add support for a timeout using Ruby’s Timeout class, which is supported all the way back to Ruby 1.8.6.
At this point, it’s definitely time to switch to named parameters, so that the code that calls this method will be more clear and readable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Example usage
Here’s an example of using the new Redis#cache
method in a Rails app to cache the results of a call to the Twitter API, with a five-sectond timeout. This example will return a default value of nil
if the Twitter API times out. But you could also pass a :default => { 'something?' }
parameter in other scenarios.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|