Class: Familia::RedisObject

Inherits:
Object
  • Object
show all
Defined in:
lib/familia/redisobject.rb

Direct Known Subclasses

HashKey, List, Set, SortedSet, String

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, opts = {}) ⇒ RedisObject

name: If parent is set, this will be used as the suffix for rediskey. Otherwise this becomes the value of the key. If this is an Array, the elements will be joined.

Options:

:class => A class that responds to Familia.load_method and Familia.dump_method. These will be used when loading and saving data from/to redis to unmarshal/marshal the class.

:reference => When true the index of the given value will be stored rather than the marshaled value. This assumes that the marshaled object is stored at a separate key. When read, from_redis looks for that separate key and returns the unmarshaled object. :class must be specified. Default: false.

:extend => Extend this instance with the functionality in an other module. Literally: “self.extend opts”.

:parent => The Familia object that this redis object belongs to. This can be a class that includes Familia or an instance.

:ttl => the time to live in seconds. When not nil, this will set the redis expire for this key whenever #save is called. You can also call it explicitly via #update_expiration.

:quantize => append a quantized timestamp to the rediskey. Takes one of the following:

Boolean: include the default stamp (now % 10 minutes)
Integer: the number of seconds to quantize to (e.g. 1.hour)
Array: All arguments for qstamp (quantum, pattern, Time.now)

:default => the default value (String-only)

:dump_method => the instance method to call to serialize the object before sending it to Redis (default: Familia.dump_method).

:load_method => the class method to call to deserialize the object after it’s read from Redis (default: Familia.load_method).

:db => the redis database to use (ignored if :redis is used).

:redis => an instance of Redis.

Uses the redis connection of the parent or the value of opts or Familia.redis (in that order).



104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/familia/redisobject.rb', line 104

def initialize name, opts={}
  @name, @opts = name, opts
  @name = @name.join(Familia.delim) if Array === @name
  #Familia.ld [name, opts, caller[0]].inspect
  self.extend @opts[:extend] if Module === @opts[:extend]
  @db = @opts.delete(:db)
  @parent = @opts.delete(:parent)
  @ttl ||= @opts.delete(:ttl) 
  @redis ||= @opts.delete(:redis)
  @cache = {}
  init if respond_to? :init
end

Class Attribute Details

.classesObject



19
20
21
# File 'lib/familia/redisobject.rb', line 19

def RedisObject.classes
  @classes
end

.db(v = nil) ⇒ Object



31
32
33
34
# File 'lib/familia/redisobject.rb', line 31

def db v=nil
  @db = v unless v.nil?
  @db || (parent ? parent.db : nil)
end

.parentObject

Returns the value of attribute parent.



25
26
27
# File 'lib/familia/redisobject.rb', line 25

def parent
  @parent
end

.ttl(v = nil) ⇒ Object



27
28
29
30
# File 'lib/familia/redisobject.rb', line 27

def ttl v=nil
  @ttl = v unless v.nil?
  @ttl || (parent ? parent.ttl : nil)
end

.uri(v = nil) ⇒ Object



35
36
37
38
# File 'lib/familia/redisobject.rb', line 35

def uri v=nil
  @uri = v unless v.nil?
  @uri || (parent ? parent.uri : Familia.uri)
end

Instance Attribute Details

#cacheObject (readonly)

RedisObject instances are frozen. ‘cache` is a hash for you to store values retreived from Redis. This is not used anywhere by default, but you’re encouraged to use it in your specific scenarios.



56
57
58
# File 'lib/familia/redisobject.rb', line 56

def cache
  @cache
end

#nameObject (readonly)

Returns the value of attribute name.



49
50
51
# File 'lib/familia/redisobject.rb', line 49

def name
  @name
end

#parentObject (readonly)

Returns the value of attribute parent.



49
50
51
# File 'lib/familia/redisobject.rb', line 49

def parent
  @parent
end

#redisObject



125
126
127
128
# File 'lib/familia/redisobject.rb', line 125

def redis
  return @redis if @redis
  parent? ? parent.redis : Familia.redis(db)
end

Class Method Details

.inherited(obj) ⇒ Object



39
40
41
42
43
44
45
46
# File 'lib/familia/redisobject.rb', line 39

def inherited(obj)
  obj.db = self.db
  obj.ttl = self.ttl
  obj.uri = self.uri
  obj.parent = self
  RedisObject.classes << obj
  super(obj)
end

.register(klass, meth) ⇒ Object

To be called inside every class that inherits RedisObject meth becomes the base for the class and instances methods that are created for the given klass (e.g. Obj.list)



11
12
13
# File 'lib/familia/redisobject.rb', line 11

def RedisObject.register klass, meth
  registration[meth] = klass
end

.registrationObject



15
16
17
# File 'lib/familia/redisobject.rb', line 15

def RedisObject.registration
  @registration
end

Instance Method Details

#class?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/familia/redisobject.rb', line 183

def class?
  !@opts[:class].to_s.empty? && @opts[:class].kind_of?(Familia)
end

#clear_cacheObject



117
118
119
# File 'lib/familia/redisobject.rb', line 117

def clear_cache
  @cache.clear
end

#dbObject

Returns the most likely value for db, checking (in this order):

* the value from :class if it's a Familia object
* the value from :parent
* the value self.class.db
* assumes the db is 0

After this is called once, this method will always return the same value.



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/familia/redisobject.rb', line 138

def db 
  # Note it's important that we select this value at the last
  # possible moment rather than in initialize b/c the value 
  # could be modified after that but before this is called. 
  if @opts[:class] && @opts[:class].ancestors.member?(Familia)
    @opts[:class].db 
  elsif parent?
    parent.db
  else
    self.class.db || @db || 0
  end
end

#deleteObject Also known as: clear, del



221
222
223
# File 'lib/familia/redisobject.rb', line 221

def delete 
  redis.del rediskey
end

#dump_methodObject



252
253
254
# File 'lib/familia/redisobject.rb', line 252

def dump_method
  @opts[:dump_method] || Familia.dump_method
end

#echo(meth, trace) ⇒ Object



121
122
123
# File 'lib/familia/redisobject.rb', line 121

def echo meth, trace
  redis.echo "[#{self.class}\##{meth}] #{trace} (#{@opts[:class]}\#)"
end

#exists?Boolean

def destroy!

clear
# TODO: delete redis objects for this instance

end

Returns:

  • (Boolean)


232
233
234
# File 'lib/familia/redisobject.rb', line 232

def exists?
  redis.exists(rediskey) && !size.zero?
end

#expire(sec) ⇒ Object



240
241
242
# File 'lib/familia/redisobject.rb', line 240

def expire sec
  redis.expire rediskey, sec.to_i
end

#expireat(unixtime) ⇒ Object



244
245
246
# File 'lib/familia/redisobject.rb', line 244

def expireat unixtime
  redis.expireat rediskey, unixtime
end

#from_redis(v) ⇒ Object



332
333
334
335
336
337
# File 'lib/familia/redisobject.rb', line 332

def from_redis v
  return @opts[:default] if v.nil?
  return v unless @opts[:class]
  ret = multi_from_redis v
  ret.first unless ret.nil? # return the object or nil
end

#load_methodObject



256
257
258
# File 'lib/familia/redisobject.rb', line 256

def load_method
  @opts[:load_method] || Familia.load_method
end

#move(db) ⇒ Object



205
206
207
# File 'lib/familia/redisobject.rb', line 205

def move db
  redis.move rediskey, db
end

#multi_from_redis(*values) ⇒ Object



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/familia/redisobject.rb', line 291

def multi_from_redis *values
  Familia.ld "multi_from_redis: (#{@opts}) #{values}"
  return [] if values.empty?
  return values.flatten unless @opts[:class]
  ret = case @opts[:class]
  when ::String
    v.to_s
  when ::Symbol
    v.to_s.to_sym
  when ::Fixnum, ::Float
    @opts[:class].induced_from v
  else
    objs = values
    
    if @opts[:reference] == true
      objs = @opts[:class].rawmultiget *values
    end
    objs.compact!
    if @opts[:class].respond_to? load_method
      objs.collect! { |obj| 
        begin
          v = @opts[:class].send load_method, obj
          if v.nil?
            Familia.ld "[#{self.class}\#multi_from_redis] nil returned for #{@opts[:class]}\##{name}" 
          end
          v
        rescue => ex
          Familia.info v
          Familia.info "Parse error for #{rediskey} (#{load_method}): #{ex.message}"
          Familia.info ex.backtrace
          nil
        end
      }
    else
      raise Familia::Problem, "No such method: #{@opts[:class]}##{load_method}"
    end
    objs.compact # don't use compact! b/c the return value appears in ret
  end
  ret
end

#parent?Boolean

Returns:

  • (Boolean)


187
188
189
# File 'lib/familia/redisobject.rb', line 187

def parent?
  Class === parent || Module === parent || parent.kind_of?(Familia)
end

#persistObject



248
249
250
# File 'lib/familia/redisobject.rb', line 248

def persist
  redis.persist rediskey
end

#qstamp(quantum = nil, pattern = nil, now = Familia.now) ⇒ Object



191
192
193
194
195
196
# File 'lib/familia/redisobject.rb', line 191

def qstamp quantum=nil, pattern=nil, now=Familia.now
  quantum ||= ttl || 10.minutes
  pattern ||= '%H%M'
  rounded = now - (now % quantum)
  Time.at(rounded).utc.strftime(pattern)
end

#realttlObject



236
237
238
# File 'lib/familia/redisobject.rb', line 236

def realttl
  redis.ttl rediskey
end

#rediskeyObject

returns a redis key based on the parent object so it will include the proper index.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/familia/redisobject.rb', line 160

def rediskey
  if parent? 
    # We need to check if the parent has a specific suffix
    # for the case where we have specified one other than :object.
    suffix = parent.kind_of?(Familia) && parent.class.suffix != :object ? parent.class.suffix : name
    k = parent.rediskey(name, nil)
  else
    k = [name].flatten.compact.join(Familia.delim)
  end
  if @opts[:quantize]
    args = case @opts[:quantize]
    when Numeric
      [@opts[:quantize]]        # :quantize => 1.minute
    when Array
      @opts[:quantize]          # :quantize => [1.day, '%m%D']
    else
      []                        # :quantize => true
    end
    k = [k, qstamp(*args)].join(Familia.delim)
  end
  k
end

#rename(newkey) ⇒ Object



209
210
211
# File 'lib/familia/redisobject.rb', line 209

def rename newkey
  redis.rename rediskey, newkey
end

#renamenx(newkey) ⇒ Object



213
214
215
# File 'lib/familia/redisobject.rb', line 213

def renamenx newkey
  redis.renamenx rediskey, newkey
end

#to_redis(v) ⇒ Object



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/familia/redisobject.rb', line 260

def to_redis v
  return v unless @opts[:class]
  ret = case @opts[:class]
  when ::Symbol, ::String, ::Fixnum, ::Float, Gibbler::Digest
    v
  else
    if ::String === v
      v
      
    elsif @opts[:reference] == true
      unless v.respond_to? :index
        raise Familia::Problem, "#{v.class} does not have an index method"
      end
      unless v.kind_of?(Familia)
        raise Familia::Problem, "#{v.class} is not Familia (#{name})"
      end
      v.index

    elsif v.respond_to? dump_method
      v.send dump_method
      
    else
      raise Familia::Problem, "No such method: #{v.class}.#{dump_method}"
    end
  end
  if ret.nil?
    Familia.ld "[#{self.class}\#to_redis] nil returned for #{@opts[:class]}\##{name}" 
  end
  ret
end

#ttlObject



151
152
153
154
155
156
# File 'lib/familia/redisobject.rb', line 151

def ttl
  @ttl || 
  (parent.ttl if parent?) || 
  (@opts[:class].ttl if class?) || 
  (self.class.ttl if self.class.respond_to?(:ttl))
end

#typeObject



217
218
219
# File 'lib/familia/redisobject.rb', line 217

def type 
  redis.type rediskey
end

#update_expiration(ttl = nil) ⇒ Object



198
199
200
201
202
203
# File 'lib/familia/redisobject.rb', line 198

def update_expiration(ttl=nil)
  ttl ||= self.ttl
  return if ttl.to_i.zero?  # nil will be zero
  Familia.ld "#{rediskey} to #{ttl}"
  expire ttl.to_i
end