A very simple, class-based namespace prefixing and encapsulation for Redis. Key features include:
- Establishes a maintainable convention by prefixing keys with the class name (e.g.
YourClass:123
) - Delegates all method calls to the redis-rb within the namespace
- Adds a better abstraction layer around Redis objects and commands
Here's an example:
class Timer < RedisClassy
def start
pipelined do
set Time.now.to_i
expire 120.seconds
end
end
def stop
del
end
def running?
!!get
end
end
timer = Timer.new(123)
timer.start
timer.running?
=> true
Timer.keys
=> ["123"]
RedisClassy.keys
=> ["Timer:123"]
The Timer class above is self-contained and more readable.
This library is made intentionally small, yet powerful when you need better abstraction on Redis objects to keep things organized.
An important message about upgrading from 1.x
With the vanilla redis
gem, you've been doing this:
redis = Redis.new
redis.set 'foo', 'bar'
redis.get 'foo' # => "bar"
With the redis-namespace
gem, you can add a prefix in the following manner:
redis_ns = Redis::Namespace.new('ns', :redis => redis)
redis_ns['foo'] = 'bar' # equivalent of => redis.set 'ns:foo', 'bar'
redis_ns['foo'] # => "bar"
Now, with the redis-classy
gem, you finally achieve a class-based naming convention:
class Something < RedisClassy
end
Something.on('foo').set('bar') # equivalent of => redis.set 'Something:foo', 'bar'
Something.on('foo').get # => "bar"
something = Something.new('foo')
something.set 'bar' # equivalent of => redis.set 'Something:foo', 'bar'
something.get # => "bar"
In Gemfile:
gem 'redis-classy'
Register the Redis server: (e.g. in config/initializers/redis_classy.rb
for Rails)
RedisClassy.redis = Redis.current
Create a class that inherits RedisClassy. (e.g. in app/redis/cache.rb
for Rails, for auto- and eager-loading)
class Cache < RedisClassy
def put(content)
pipelined do
set content
expire 5.seconds
end
end
end
cache = Cache.new(123)
cache.put "This tape will self-destruct in five seconds. Good luck."
Since the on
method is added as a syntactic sugar for new
, you can also run a command in one shot as well:
Cache.on(123).put
For convenience, singleton and predefined static keys are also supported.
class Counter < RedisClassy
singleton
end
Counter.incr # 'Counter:singleton' => '1'
Counter.incr # 'Counter:singleton' => '2'
Counter.get # => '2'
class Stats < RedisClassy
singletons :median, :average
end
ages = [21,22,24,28,30]
Stats.median.set ages[ages.size/2] # 'Stats:median' => '24'
Stats.average.set ages.inject(:+)/ages.size # 'Stats:average' => '25'
Stats.median.get # => '24'
Stats.average.get # => '25'
Finally, you can also pass an arbitrary object that responds to id
as a key. This is useful when used in combination with ActiveRecord, etc.
class Lock < RedisClassy
end
class Room < ActiveRecord::Base
end
room = Room.create
lock = Lock.new(room)
When you need an access to the non-namespaced, raw Redis keys, it's available as RedisClass.keys
. Keep in mind that this method is very slow at O(N) computational complexity and potentially hazardous when you have many keys. Read the details.
RedisClassy.keys
=> ["Stats:median", "Stats:average", "Counter"]
RedisClassy.keys 'Stats:*'
=> ["Stats:median", "Stats:average"]
Since the redis
attribute is a class instance variable, you can dynamically assign different databases for each class, without affecting other classes.
Cache.redis = Redis::Namespace.new('Cache', redis: Redis.new(host: 'another.host'))
If you run fork-based app servers such as Unicorn or Passenger, you need to reconnect to the Redis after forking.
after_fork do
RedisClassy.redis.client.reconnect
end
Note that since Redis Classy assigns a namespaced Redis instance upon the inheritance event of each subclass (class Something < RedisClassy
), reconnecting the master (non-namespaced) connection that is referenced from all subclasses should probably be the safest and the most efficient way to survive a forking event.
Dependency:
Use case: