familia 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,475 @@
1
+
2
+ module Familia::Object
3
+
4
+ class RedisObject
5
+
6
+ @klasses = {}
7
+ class << self
8
+ attr_reader :klasses
9
+ # To be called inside every class that inherits RedisObject
10
+ def register(kind, klass)
11
+ klasses[kind] = klass
12
+ end
13
+ end
14
+
15
+ attr_reader :name, :parent
16
+ def initialize n, p, opts={}
17
+ @name, @parent = n, p
18
+ @opts = opts
19
+ init if respond_to? :init
20
+ end
21
+
22
+ # returns a redis key based on the parent
23
+ # object so it will include the proper index.
24
+ def rediskey
25
+ parent.rediskey(name)
26
+ end
27
+
28
+ def redis
29
+ parent.redis
30
+ end
31
+
32
+ def update_expiration(ttl=nil)
33
+ ttl ||= @opts[:ttl]
34
+ return unless ttl && ttl.to_i > 0
35
+ #Familia.trace :SET_EXPIRE, Familia.redis(self.class.uri), "#{rediskey} to #{self.ttl}"
36
+ expire ttl.to_i
37
+ end
38
+
39
+ def move db
40
+ redis.move rediskey, db
41
+ end
42
+
43
+ def destroy!
44
+ clear
45
+ # TODO: delete redis objects for this instance
46
+ end
47
+
48
+ # TODO: rename, renamenx
49
+
50
+ def type
51
+ redis.type rediskey
52
+ end
53
+
54
+ def clear
55
+ redis.del rediskey
56
+ end
57
+ alias_method :delete, :clear
58
+
59
+ def exists?
60
+ !size.zero?
61
+ end
62
+
63
+ def expire sec
64
+ redis.expire rediskey, sec.to_i
65
+ end
66
+
67
+ def expireat unixtime
68
+ redis.expireat rediskey, unixtime
69
+ end
70
+
71
+ def to_redis v
72
+ return v unless @opts[:class]
73
+ if v.respond_to? Familia.dump_method
74
+ v.send Familia.dump_method
75
+ else
76
+ Familia.ld "No such method: #{v.class}.#{Familia.dump_method}"
77
+ v
78
+ end
79
+ end
80
+
81
+ def from_redis v
82
+ return v unless @opts[:class]
83
+ case @opts[:class]
84
+ when String
85
+ v.to_s
86
+ when Fixnum, Float
87
+ @opts[:class].induced_from v
88
+ else
89
+ if @opts[:class].method_defined? Familia.load_method
90
+ @opts[:class].send Familia.load_method, v
91
+ else
92
+ Familia.ld "No such method: #{@opts[:class]}##{Familia.load_method}"
93
+ v
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+
101
+ class List < RedisObject
102
+
103
+ def size
104
+ redis.llen rediskey
105
+ end
106
+ alias_method :length, :size
107
+
108
+ def << v
109
+ redis.rpush rediskey, to_redis(v)
110
+ redis.ltrim rediskey, -@opts[:maxlength], -1 if @opts[:maxlength]
111
+ self
112
+ end
113
+ alias_method :push, :<<
114
+
115
+ def unshift v
116
+ redis.lpush rediskey, to_redis(v)
117
+ # TODO: test maxlength
118
+ redis.ltrim rediskey, 0, @opts[:maxlength] - 1 if @opts[:maxlength]
119
+ self
120
+ end
121
+
122
+ def pop
123
+ from_redis redis.rpop(rediskey)
124
+ end
125
+
126
+ def shift
127
+ from_redis redis.lpop(key)
128
+ end
129
+
130
+ def [] idx, count=nil
131
+ if idx.is_a? Range
132
+ range idx.first, idx.last
133
+ elsif count
134
+ case count <=> 0
135
+ when 1 then range(idx, idx + count - 1)
136
+ when 0 then []
137
+ when -1 then nil
138
+ end
139
+ else
140
+ at idx
141
+ end
142
+ end
143
+ alias_method :slice, :[]
144
+
145
+ def delete v, count=0
146
+ redis.lrem rediskey, count, to_redis(v)
147
+ end
148
+ alias_method :remove, :delete
149
+
150
+ def range sidx=0, eidx=-1
151
+ # TODO: handle @opts[:marshal]
152
+ redis.lrange rediskey, sidx, eidx
153
+ end
154
+ alias_method :to_a, :range
155
+
156
+ def at idx
157
+ from_redis redis.lindex(rediskey, idx)
158
+ end
159
+
160
+ def first
161
+ at 0
162
+ end
163
+
164
+ def last
165
+ at -1
166
+ end
167
+
168
+ # TODO: def replace
169
+ ## Make the value stored at KEY identical to the given list
170
+ #define_method :"#{name}_sync" do |*latest|
171
+ # latest = latest.flatten.compact
172
+ # # Do nothing if we're given an empty Array.
173
+ # # Otherwise this would clear all current values
174
+ # if latest.empty?
175
+ # false
176
+ # else
177
+ # # Convert to a list of index values if we got the actual objects
178
+ # latest = latest.collect { |obj| obj.index } if klass === latest.first
179
+ # current = send("#{name_plural}raw")
180
+ # added = latest-current
181
+ # removed = current-latest
182
+ # #Familia.info "#{self.index}: adding: #{added}"
183
+ # added.each { |v| self.send("add_#{name_singular}", v) }
184
+ # #Familia.info "#{self.index}: removing: #{removed}"
185
+ # removed.each { |v| self.send("remove_#{name_singular}", v) }
186
+ # true
187
+ # end
188
+ #end
189
+
190
+ Familia::Object::RedisObject.register :list, self
191
+ end
192
+
193
+ class Set < RedisObject
194
+
195
+ def size
196
+ redis.scard rediskey
197
+ end
198
+ alias_method :length, :size
199
+
200
+ def << v
201
+ redis.sadd rediskey, to_redis(v)
202
+ self
203
+ end
204
+ alias_method :add, :<<
205
+
206
+ def members
207
+ # TODO: handle @opts[:marshal]
208
+ redis.smembers rediskey
209
+ end
210
+ alias_method :to_a, :members
211
+
212
+ def member? v
213
+ redis.sismember rediskey, to_redis(v)
214
+ end
215
+ alias_method :include?, :member?
216
+
217
+ def delete v
218
+ redis.srem rediskey, to_redis(v)
219
+ end
220
+
221
+ def intersection *setkeys
222
+ # TODO
223
+ end
224
+
225
+ ## Make the value stored at KEY identical to the given list
226
+ #define_method :"#{name}_sync" do |*latest|
227
+ # latest = latest.flatten.compact
228
+ # # Do nothing if we're given an empty Array.
229
+ # # Otherwise this would clear all current values
230
+ # if latest.empty?
231
+ # false
232
+ # else
233
+ # # Convert to a list of index values if we got the actual objects
234
+ # latest = latest.collect { |obj| obj.index } if klass === latest.first
235
+ # current = send("#{name_plural}raw")
236
+ # added = latest-current
237
+ # removed = current-latest
238
+ # #Familia.info "#{self.index}: adding: #{added}"
239
+ # added.each { |v| self.send("add_#{name_singular}", v) }
240
+ # #Familia.info "#{self.index}: removing: #{removed}"
241
+ # removed.each { |v| self.send("remove_#{name_singular}", v) }
242
+ # true
243
+ # end
244
+ #end
245
+
246
+ Familia::Object::RedisObject.register :set, self
247
+ end
248
+
249
+ class SortedSet < RedisObject
250
+
251
+ def size
252
+ redis.zcard rediskey
253
+ end
254
+ alias_method :length, :size
255
+
256
+ # NOTE: The argument order is the reverse of #add
257
+ # e.g. obj.metrics[VALUE] = SCORE
258
+ def []= v, score
259
+ add score, v
260
+ end
261
+
262
+ # NOTE: The argument order is the reverse of #[]=
263
+ def add score, v
264
+ redis.zadd rediskey, score, to_redis(v)
265
+ end
266
+
267
+ def score v
268
+ ret = redis.zscore rediskey, to_redis(v)
269
+ ret.nil? ? nil : ret.to_f
270
+ end
271
+ alias_method :[], :score
272
+
273
+ def member? v
274
+ !rank(v).nil?
275
+ end
276
+ alias_method :include?, :member?
277
+
278
+ # rank of member +v+ when ordered lowest to highest (starts at 0)
279
+ def rank v
280
+ ret = redis.zrank rediskey, to_redis(v)
281
+ ret.nil? ? nil : ret.to_i
282
+ end
283
+
284
+ # rank of member +v+ when ordered highest to lowest (starts at 0)
285
+ def revrank v
286
+ ret = redis.zrevrank rediskey, to_redis(v)
287
+ ret.nil? ? nil : ret.to_i
288
+ end
289
+
290
+ def members opts={}
291
+ range 0, -1, opts
292
+ end
293
+ alias_method :to_a, :members
294
+
295
+ def membersrev opts={}
296
+ rangerev 0, -1, opts
297
+ end
298
+
299
+ def range sidx, eidx, opts={}
300
+ opts[:with_scores] = true if opts[:withscores]
301
+ redis.zrange rediskey, sidx, eidx, opts
302
+ end
303
+
304
+ def rangerev sidx, eidx, opts={}
305
+ opts[:with_scores] = true if opts[:withscores]
306
+ redis.zrevrange rediskey, sidx, eidx, opts
307
+ end
308
+
309
+ # e.g. obj.metrics.rangebyscore (now-12.hours), now, :limit => [0, 10]
310
+ def rangebyscore sscore, escore, opts={}
311
+ opts[:with_scores] = true if opts[:withscores]
312
+ redis.zrangebyscore rediskey, sscore, escore, opts
313
+ end
314
+
315
+ def remrangebyrank srank, erank
316
+ redis.zremrangebyrank rediskey, srank, erank
317
+ end
318
+
319
+ def remrangebyscore sscore, escore
320
+ redis.zremrangebyscore rediskey, sscore, escore
321
+ end
322
+
323
+ def increment v, by=1
324
+ redis.zincrby(rediskey, by, v).to_i
325
+ end
326
+ alias_method :incr, :increment
327
+ alias_method :incrby, :increment
328
+
329
+ def decrement v, by=1
330
+ increment v, -by
331
+ end
332
+ alias_method :decr, :decrement
333
+ alias_method :decrby, :decrement
334
+
335
+ def delete v
336
+ redis.zrem rediskey, to_redis(v)
337
+ end
338
+ alias_method :remove, :delete
339
+
340
+ def at idx
341
+ range(idx, idx).first
342
+ end
343
+
344
+ # Return the first element in the list. Redis: ZRANGE(0)
345
+ def first
346
+ at(0)
347
+ end
348
+
349
+ # Return the last element in the list. Redis: ZRANGE(-1)
350
+ def last
351
+ at(-1)
352
+ end
353
+
354
+ Familia::Object::RedisObject.register :zset, self
355
+ end
356
+
357
+ class HashKey < RedisObject
358
+
359
+ def size
360
+ redis.hlen rediskey
361
+ end
362
+ alias_method :length, :size
363
+
364
+ def []= n, v
365
+ redis.hset rediskey, n, to_redis(v)
366
+ end
367
+ alias_method :store, :[]=
368
+
369
+ def [] n
370
+ redis.hget rediskey, n
371
+ end
372
+
373
+ def fetch n, default=nil
374
+ ret = self[n]
375
+ if ret.nil?
376
+ raise IndexError.new("No such index for: #{n}") if default.nil?
377
+ default
378
+ else
379
+ ret
380
+ end
381
+ end
382
+
383
+ def keys
384
+ redis.hkeys rediskey
385
+ end
386
+
387
+ def values
388
+ redis.hvals rediskey
389
+ end
390
+
391
+ def all
392
+ redis.hgetall rediskey
393
+ end
394
+ alias_method :to_hash, :all
395
+ alias_method :clone, :all
396
+
397
+ def has_key? n
398
+ redis.hexists rediskey, n
399
+ end
400
+ alias_method :include?, :has_key?
401
+ alias_method :member?, :has_key?
402
+
403
+ def delete n
404
+ redis.hdel rediskey, n
405
+ end
406
+ alias_method :remove, :delete
407
+ alias_method :rem, :delete
408
+ alias_method :del, :delete
409
+
410
+ def increment n, by=1
411
+ redis.hincrby(rediskey, n, by).to_i
412
+ end
413
+ alias_method :incr, :increment
414
+ alias_method :incrby, :increment
415
+
416
+ def decrement n, by=1
417
+ increment n, -by
418
+ end
419
+ alias_method :decr, :decrement
420
+ alias_method :decrby, :decrement
421
+
422
+ def update h={}
423
+ raise ArgumentError, "Argument to bulk_set must be a hash" unless Hash === h
424
+ redis.hmset(rediskey, h.inject([]){ |ret,pair| ret + [pair[0], to_redis(pair[1])] })
425
+ end
426
+ alias_method :merge!, :update
427
+
428
+ def values_at *names
429
+ redis.hmget rediskey, *names.flatten.compact
430
+ end
431
+
432
+ Familia::Object::RedisObject.register :hash, self
433
+ end
434
+
435
+ class String < RedisObject
436
+
437
+ def init
438
+ redis.setnx rediskey, @opts[:default] if @opts[:default]
439
+ end
440
+
441
+ def size
442
+ to_s.size
443
+ end
444
+ alias_method :length, :size
445
+
446
+ def value
447
+ redis.get rediskey
448
+ end
449
+ alias_method :get, :value
450
+
451
+ def to_s
452
+ value.to_s # value can return nil which to_s should not
453
+ end
454
+
455
+ def value= v
456
+ redis.set rediskey, to_redis(v)
457
+ to_redis(v)
458
+ end
459
+ alias_method :replace, :value=
460
+ alias_method :set, :value=
461
+
462
+ def nil?
463
+ value.nil?
464
+ end
465
+
466
+ Familia::Object::RedisObject.register :string, self
467
+ end
468
+
469
+
470
+ class Counter < RedisObject
471
+
472
+ #Familia::Object::RedisObject.register :counter, self
473
+ end
474
+
475
+ end