Class: Authorize::Redis::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/authorize/redis/base.rb

Overview

The key feature of this module is that it presents a coherent view of the database in memory. For each database entry, at most one in-memory Ruby object will exist, and all state for the object will be atomically persisted to the database. This behavior introduces the following constraints:

1.  The database is viewed through an identity map (http://en.wikipedia.org/wiki/Identity_map) to
    ensure in-thread coherency.  Consequently, the record's key must be known prior to initialization,
    allowing new objects to be instantiated only if no previously instantiated object with that key is
    already in memory.
2.  In order to allow Redis::Base#initialize to set values (which are atomically persisted), the id must
    be available at the _start_ of initialization.  This is accomplished by overriding Redis.new and
    assigning the id immediately after allocation.

TODO: YAML serialization (groups.google.com/group/comp.lang.ruby/browse_thread/thread/c855253c9d8f482e)

Direct Known Subclasses

Array, Hash, Set, String

Constant Summary collapse

NAMESPACE_SEPARATOR =
'::'

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args, &block) ⇒ Object



147
148
149
150
151
# File 'lib/authorize/redis/base.rb', line 147

def method_missing(m, *args, &block)
  proxy = __getobj__ # Performance tweak
  return super unless proxy.respond_to?(m) # If there is going to be an explosion, let superclass handle it.
  proxy.freeze.__send__(m, *args, &block) # Ensure no state can be changed and send the method on its way.
end

Class Attribute Details

.connection_specification=(value) ⇒ Object (writeonly)

Sets the attribute connection_specification

Parameters:

  • value

    the value to set the attribute connection_specification to.



19
20
21
# File 'lib/authorize/redis/base.rb', line 19

def connection_specification=(value)
  @connection_specification = value
end

.loggerObject



37
38
39
# File 'lib/authorize/redis/base.rb', line 37

def self.logger
  @logger ||= (@base ? nil : superclass.logger)
end

Instance Attribute Details

#idObject (readonly) Also known as: to_s

Returns the value of attribute id.



84
85
86
# File 'lib/authorize/redis/base.rb', line 84

def id
  @id
end

Class Method Details

._load(id) ⇒ Object



82
# File 'lib/authorize/redis/base.rb', line 82

def self._load(id);load(id);end

.connectionObject Also known as: db



31
32
33
# File 'lib/authorize/redis/base.rb', line 31

def connection
  connection_manager.connection
end

.connection_base?Boolean

Should this class establish a connection instead of relying on a superclass’ connection?

Returns:

  • (Boolean)


22
23
24
# File 'lib/authorize/redis/base.rb', line 22

def connection_base?
  @base || @connection_specification
end

.connection_managerObject

Search up the inheritance chain for a manager unless a connection is specified here.



27
28
29
# File 'lib/authorize/redis/base.rb', line 27

def connection_manager
  @manager ||= (connection_base? ? Redis::ConnectionManager.new(@connection_specification) : superclass.connection_manager)
end

.exists?(id) ⇒ Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/authorize/redis/base.rb', line 57

def self.exists?(id)
  db.exists(id)
end

.generate_keyObject



49
50
51
# File 'lib/authorize/redis/base.rb', line 49

def self.generate_key
  subordinate_key(name, next_counter(name))
end

.indexObject



53
54
55
# File 'lib/authorize/redis/base.rb', line 53

def self.index
  @index ||= ::Hash.new
end

.load(id) ⇒ Object



76
77
78
79
80
81
# File 'lib/authorize/redis/base.rb', line 76

def self.load(id)
  index[id] ||= allocate.tap do |o|
    o.instance_variable_set(:@id, id)
    o.send(:reload)
  end
end

.load_all(namespace = name) ⇒ Object

Load all model objects in the given namespace



62
63
64
65
66
# File 'lib/authorize/redis/base.rb', line 62

def self.load_all(namespace = name)
  redis_glob = subordinate_key(namespace, '*')
  re = Regexp.new(subordinate_key(namespace, ".+(?=#{NAMESPACE_SEPARATOR})"))
  db.keys(redis_glob).map{|m| m.slice(re)}.map{|id| load(id)}
end

.new(id = nil, *args, &block) ⇒ Object



68
69
70
71
72
73
74
# File 'lib/authorize/redis/base.rb', line 68

def self.new(id = nil, *args, &block)
  id ||= generate_key
  index[id] = allocate.tap do |o|
    o.instance_variable_set(:@id, id)
    o.send(:initialize, *args, &block)
  end
end

.next_counter(key) ⇒ Object



45
46
47
# File 'lib/authorize/redis/base.rb', line 45

def self.next_counter(key)
  db.incr(key)
end

.subordinate_key(*keys) ⇒ Object



41
42
43
# File 'lib/authorize/redis/base.rb', line 41

def self.subordinate_key(*keys)
  keys.compact.join(NAMESPACE_SEPARATOR)
end

Instance Method Details

#==(other) ⇒ Object



103
104
105
# File 'lib/authorize/redis/base.rb', line 103

def ==(other)
  id.eql?(other.id) && other.is_a?(self.class)
end

#__getobj__Object

Methods that don’t change the state of the object can safely delegate to a Ruby proxy object



143
144
145
# File 'lib/authorize/redis/base.rb', line 143

def __getobj__
  raise "Abstract class requires implementation"
end

#_dump(depth = nil) ⇒ Object



117
118
119
# File 'lib/authorize/redis/base.rb', line 117

def _dump(depth = nil)
  id
end

#dbObject



91
92
93
# File 'lib/authorize/redis/base.rb', line 91

def db
  self.class.db
end

#destroyObject



128
129
130
131
132
# File 'lib/authorize/redis/base.rb', line 128

def destroy
  db.del(id) # This operation will remove all native Redis types (String, Hash, List, Set, etc.) in one shot.
  self.class.index.delete(id)
  freeze
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


95
96
97
# File 'lib/authorize/redis/base.rb', line 95

def eql?(other)
  id.eql?(other.id) && other.is_a?(self.class)
end

#exists?Boolean

Returns:

  • (Boolean)


134
135
136
# File 'lib/authorize/redis/base.rb', line 134

def exists?
  self.class.exists?(id)
end

#hashObject



99
100
101
# File 'lib/authorize/redis/base.rb', line 99

def hash
  id.hash
end

#loggerObject



87
88
89
# File 'lib/authorize/redis/base.rb', line 87

def logger
  self.class.logger
end

#reloadObject

This hook restores a re-instantiated object that has previously been initialized and then persisted. Non-idempotent operations should be used with great care.



115
# File 'lib/authorize/redis/base.rb', line 115

def reload;end

#respond_to?(m, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


153
154
155
156
# File 'lib/authorize/redis/base.rb', line 153

def respond_to?(m, include_private = false)
  return true if super
  __getobj__.respond_to?(m, include_private)
end

#subordinate_key(name, counter = false) ⇒ Object

Note that requesting a counter value “steals” from the class counter.



108
109
110
111
# File 'lib/authorize/redis/base.rb', line 108

def subordinate_key(name, counter = false)
  k = self.class.subordinate_key(id, name)
  counter ? self.class.subordinate_key(k, self.class.next_counter(k)) : k
end

#to_yaml(opts = {}) ⇒ Object

Emit this Redis object with a a magic type and simple scalar identifier. The (poorly documented) “type id” format allows for a succinct one-line YAML expression for a Redis instance (no indented attributes hash required) which in turn simplifies automatic YAMLification of collections of Redis objects. Arguably, it’s more readable as well.



124
125
126
# File 'lib/authorize/redis/base.rb', line 124

def to_yaml(opts = {})
  YAML.quick_emit(self.id, opts) {|out| out.scalar("tag:hapgoods.com,2010-08-11:#{self.class.name}", id)}
end

#valid?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'lib/authorize/redis/base.rb', line 138

def valid?
  raise "Abstract class requires implementation"
end