Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
191 lines (168 sloc) 6.286 kb
# Redis::Objects - Lightweight object layer around redis-rb
# See README.rdoc for usage and approach.
require 'redis'
require 'redis/objects/connection_pool_proxy'
class Redis
autoload :Counter, 'redis/counter'
autoload :List, 'redis/list'
autoload :Lock, 'redis/lock'
autoload :Set, 'redis/set'
autoload :SortedSet, 'redis/sorted_set'
autoload :Value, 'redis/value'
autoload :HashKey, 'redis/hash_key'
#
# Redis::Objects enables high-performance atomic operations in your app
# by leveraging the atomic features of the Redis server. To use Redis::Objects,
# first include it in any class you want. (This example uses an ActiveRecord
# subclass, but that is *not* required.) Then, use +counter+, +lock+, +set+, etc
# to define your primitives:
#
# class Game < ActiveRecord::Base
# include Redis::Objects
#
# counter :joined_players
# counter :active_players, :key => 'game:#{id}:act_plyr'
# lock :archive_game
# set :player_ids
# end
#
# The, you can use these counters both for bookeeping and as atomic actions:
#
# @game = Game.find(id)
# @game_user = @game.joined_players.increment do |val|
# break if val > @game.max_players
# gu = @game.game_users.create!(:user_id => @user.id)
# @game.active_players.increment
# gu
# end
# if @game_user.nil?
# # game is full - error screen
# else
# # success
# end
#
#
#
module Objects
dir = File.expand_path(__FILE__.sub(/\.rb$/,''))
autoload :Counters, 'redis/objects/counters'
autoload :Lists, 'redis/objects/lists'
autoload :Locks, 'redis/objects/locks'
autoload :Sets, 'redis/objects/sets'
autoload :SortedSets, 'redis/objects/sorted_sets'
autoload :Values, 'redis/objects/values'
autoload :Hashes, 'redis/objects/hashes'
class NotConnected < StandardError; end
class NilObjectId < StandardError; end
class << self
def redis=(conn)
@redis = Objects::ConnectionPoolProxy.proxy_if_needed(conn)
end
def redis
@redis || $redis || Redis.current ||
raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
end
def included(klass)
# Core (this file)
klass.instance_variable_set('@redis', nil)
klass.instance_variable_set('@redis_objects', {})
klass.send :include, InstanceMethods
klass.extend ClassMethods
# Pull in each object type
klass.send :include, Redis::Objects::Counters
klass.send :include, Redis::Objects::Lists
klass.send :include, Redis::Objects::Locks
klass.send :include, Redis::Objects::Sets
klass.send :include, Redis::Objects::SortedSets
klass.send :include, Redis::Objects::Values
klass.send :include, Redis::Objects::Hashes
end
end
# Class methods that appear in your class when you include Redis::Objects.
module ClassMethods
# Enable per-class connections (eg, User and Post can use diff redis-server)
def redis=(conn)
@redis = Objects::ConnectionPoolProxy.proxy_if_needed(conn)
end
def redis
@redis || Objects.redis
end
# Internal list of objects
attr_writer :redis_objects
def redis_objects
@redis_objects ||= {}
end
# Set the Redis redis_prefix to use. Defaults to model_name
def redis_prefix=(redis_prefix) @redis_prefix = redis_prefix end
def redis_prefix(klass = self) #:nodoc:
@redis_prefix ||= klass.name.to_s.
sub(%r{(.*::)}, '').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
downcase
end
def redis_options(name)
klass = first_ancestor_with(name)
return klass.redis_objects[name.to_sym] || {}
end
def redis_field_redis(name) #:nodoc:
klass = first_ancestor_with(name)
override_redis = klass.redis_objects[name.to_sym][:redis]
if override_redis
Objects::ConnectionPoolProxy.proxy_if_needed(override_redis)
else
self.redis
end
end
def redis_field_key(name, id=nil, context=self) #:nodoc:
klass = first_ancestor_with(name)
# READ THIS: This can never ever ever ever change or upgrades will corrupt all data
# I don't think people were using Proc as keys before (that would create a weird key). Should be ok
if key = klass.redis_objects[name.to_sym][:key]
if key.respond_to?(:call)
key = key.call context
else
context.instance_eval "%(#{key})"
end
else
if id.nil? and !klass.redis_objects[name.to_sym][:global]
raise NilObjectId,
"[#{klass.redis_objects[name.to_sym]}] Attempt to address redis-object " +
":#{name} on class #{klass.name} with nil id (unsaved record?) [object_id=#{object_id}]"
end
"#{redis_prefix(klass)}:#{id}:#{name}"
end
end
def first_ancestor_with(name)
if redis_objects && redis_objects.key?(name.to_sym)
self
elsif superclass && superclass.respond_to?(:redis_objects)
superclass.first_ancestor_with(name)
end
end
def redis_id_field(id=nil)
@redis_id_field = id || @redis_id_field
if superclass && superclass.respond_to?(:redis_id_field)
@redis_id_field ||= superclass.redis_id_field
end
@redis_id_field ||= :id
end
end
# Instance methods that appear in your class when you include Redis::Objects.
module InstanceMethods
# Map up one level to make modular extend/include approach sane
def redis() self.class.redis end
def redis_objects() self.class.redis_objects end
def redis_options(name) #:nodoc:
return self.class.redis_options(name)
end
def redis_field_redis(name) #:nodoc:
return self.class.redis_field_redis(name)
end
def redis_field_key(name) #:nodoc:
id = send(self.class.redis_id_field)
self.class.redis_field_key(name, id, self)
end
end
end
end
Jump to Line
Something went wrong with that request. Please try again.