familia 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -47,7 +47,7 @@ module Familia
47
47
  end
48
48
 
49
49
  attr_reader :name, :parent
50
- attr_accessor :redis
50
+ attr_writer :redis
51
51
 
52
52
  # +name+: If parent is set, this will be used as the suffix
53
53
  # for rediskey. Otherwise this becomes the value of the key.
@@ -55,7 +55,20 @@ module Familia
55
55
  #
56
56
  # Options:
57
57
  #
58
- # :parent: The Familia object that this redis object belongs
58
+ # :class => A class that responds to Familia.load_method and
59
+ # Familia.dump_method. These will be used when loading and
60
+ # saving data from/to redis to unmarshal/marshal the class.
61
+ #
62
+ # :reference => When true the index of the given value will be
63
+ # stored rather than the marshaled value. This assumes that
64
+ # the marshaled object is stored at a separate key. When read,
65
+ # from_redis looks for that separate key and returns the
66
+ # unmarshaled object. :class must be specified. Default: false.
67
+ #
68
+ # :extend => Extend this instance with the functionality in an
69
+ # other module. Literally: "self.extend opts[:extend]".
70
+ #
71
+ # :parent => The Familia object that this redis object belongs
59
72
  # to. This can be a class that includes Familia or an instance.
60
73
  #
61
74
  # :ttl => the time to live in seconds. When not nil, this will
@@ -64,10 +77,6 @@ module Familia
64
77
  #
65
78
  # :default => the default value (String-only)
66
79
  #
67
- # :class => A class that responds to Familia.load_method and
68
- # Familia.dump_method. These will be used when loading and
69
- # saving data from/to redis to unmarshal/marshal the class.
70
- #
71
80
  # :db => the redis database to use (ignored if :redis is used).
72
81
  #
73
82
  # :redis => an instance of Redis.
@@ -77,28 +86,60 @@ module Familia
77
86
  def initialize name, opts={}
78
87
  @name, @opts = name, opts
79
88
  @name = @name.join(Familia.delim) if Array === @name
80
- @opts[:ttl] ||= self.class.ttl
81
- @opts[:db] ||= self.class.db
89
+ Familia.ld [name, opts, caller[0]].inspect
90
+ self.extend @opts[:extend] if Module === @opts[:extend]
91
+ @db = @opts.delete(:db)
92
+ @ttl = @opts.delete(:ttl)
82
93
  @parent = @opts.delete(:parent)
83
- @redis = parent.redis if parent?
84
- @redis ||= @opts.delete(:redis) || Familia.redis(@opts[:db])
94
+ @redis ||= @opts.delete(:redis)
85
95
  init if respond_to? :init
86
96
  end
87
97
 
98
+ def redis
99
+ return @redis if @redis
100
+ parent? ? parent.redis : Familia.redis(db)
101
+ end
102
+
103
+ def ttl
104
+ @ttl || self.class.ttl
105
+ end
106
+
107
+ # Returns the most likely value for db, checking (in this order):
108
+ # * the value from :class if it's a Familia object
109
+ # * the value from :parent
110
+ # * the value self.class.db
111
+ # * assumes the db is 0
112
+ #
113
+ # After this is called once, this method will always return the
114
+ # same value.
115
+ def db
116
+ return @db if @db
117
+ # Note it's important that we select this value at the last
118
+ # possible moment rather than in initialize b/c the value
119
+ # could be modified after that but before this is called.
120
+ if @opts[:class] && @opts[:class].ancestors.member?(Familia)
121
+ @db = @opts[:class].db
122
+ else
123
+ @db = parent? ? parent.db : self.class.db
124
+ end
125
+ @db ||= 0
126
+ @db
127
+ end
128
+
88
129
  # returns a redis key based on the parent
89
130
  # object so it will include the proper index.
90
131
  def rediskey
91
- parent? ? parent.rediskey(name) : Familia.rediskey(name)
132
+ parent? ? parent.rediskey(name) : [name].flatten.compact.join(Familia.delim)
92
133
  end
93
134
 
94
135
  def parent?
95
- Class === parent || parent.kind_of?(Familia)
136
+ Class === parent || Module === parent || parent.kind_of?(Familia)
96
137
  end
97
138
 
98
139
  def update_expiration(ttl=nil)
99
- ttl ||= @opts[:ttl]
140
+ ttl ||= @opts[:ttl] || self.class.ttl
100
141
  return unless ttl && ttl.to_i > 0
101
- #Familia.trace :SET_EXPIRE, Familia.redis(self.class.uri), "#{rediskey} to #{self.ttl}"
142
+ Familia.info "#{rediskey} to #{ttl}"
102
143
  expire ttl.to_i
103
144
  end
104
145
 
@@ -124,10 +165,10 @@ module Familia
124
165
  #end
125
166
 
126
167
  def exists?
127
- !size.zero?
168
+ redis.exists(rediskey) && !size.zero?
128
169
  end
129
170
 
130
- def ttl
171
+ def realttl
131
172
  redis.ttl rediskey
132
173
  end
133
174
 
@@ -139,17 +180,38 @@ module Familia
139
180
  redis.expireat rediskey, unixtime
140
181
  end
141
182
 
183
+ def dump_method
184
+ @opts[:dump_method] || Familia.dump_method
185
+ end
186
+
187
+ def load_method
188
+ @opts[:load_method] || Familia.load_method
189
+ end
190
+
142
191
  def to_redis v
143
192
  return v unless @opts[:class]
144
- if v.respond_to? Familia.dump_method
145
- v.send Familia.dump_method
146
- else
147
- Familia.ld "No such method: #{v.class}.#{Familia.dump_method}"
193
+ case @opts[:class]
194
+ when String, Fixnum, Float, Gibbler::Digest
148
195
  v
196
+ else
197
+ if @opts[:reference] == true
198
+ unless v.respond_to? :index
199
+ raise Familia::Problem, "#{v.class} does not have an index method"
200
+ end
201
+ unless v.kind_of?(Familia)
202
+ raise Familia::Problem, "#{v.class} is not Familia (#{name})"
203
+ end
204
+ v.index
205
+ elsif v.respond_to? dump_method
206
+ v.send dump_method
207
+ else
208
+ raise Familia::Problem, "No such method: #{v.class}.#{dump_method}"
209
+ end
149
210
  end
150
211
  end
151
212
 
152
213
  def from_redis v
214
+ return @opts[:default] if v.nil?
153
215
  return v unless @opts[:class]
154
216
  case @opts[:class]
155
217
  when String
@@ -157,13 +219,21 @@ module Familia
157
219
  when Fixnum, Float
158
220
  @opts[:class].induced_from v
159
221
  else
160
- if @opts[:class].method_defined? Familia.load_method
161
- @opts[:class].send Familia.load_method, v
222
+ if @opts[:reference] == true
223
+ @opts[:class].from_redis v
162
224
  else
163
- Familia.ld "No such method: #{@opts[:class]}##{Familia.load_method}"
164
- v
225
+ if @opts[:class].respond_to? load_method
226
+ @opts[:class].send load_method, v
227
+ else
228
+ raise Familia::Problem, "No such method: #{@opts[:class]}##{load_method}"
229
+ end
165
230
  end
166
231
  end
232
+ rescue => ex
233
+ Familia.info v
234
+ Familia.info "Parse error for #{rediskey} (#{load_method}): #{ex.message}"
235
+ Familia.info ex.backtrace
236
+ nil
167
237
  end
168
238
 
169
239
  end
@@ -176,15 +246,22 @@ module Familia
176
246
  end
177
247
  alias_method :length, :size
178
248
 
179
- def << v
180
- redis.rpush rediskey, to_redis(v)
249
+ def empty?
250
+ size == 0
251
+ end
252
+
253
+ def push *values
254
+ values.flatten.compact.each { |v| redis.rpush rediskey, to_redis(v) }
181
255
  redis.ltrim rediskey, -@opts[:maxlength], -1 if @opts[:maxlength]
182
256
  self
183
257
  end
184
- alias_method :push, :<<
185
-
186
- def unshift v
187
- redis.lpush rediskey, to_redis(v)
258
+
259
+ def << v
260
+ push v
261
+ end
262
+
263
+ def unshift *values
264
+ values.flatten.compact.each { |v| redis.lpush rediskey, to_redis(v) }
188
265
  # TODO: test maxlength
189
266
  redis.ltrim rediskey, 0, @opts[:maxlength] - 1 if @opts[:maxlength]
190
267
  self
@@ -195,7 +272,7 @@ module Familia
195
272
  end
196
273
 
197
274
  def shift
198
- from_redis redis.lpop(key)
275
+ from_redis redis.lpop(rediskey)
199
276
  end
200
277
 
201
278
  def [] idx, count=nil
@@ -217,12 +294,40 @@ module Familia
217
294
  redis.lrem rediskey, count, to_redis(v)
218
295
  end
219
296
  alias_method :remove, :delete
297
+ alias_method :rem, :delete
298
+ alias_method :del, :delete
220
299
 
221
300
  def range sidx=0, eidx=-1
222
- # TODO: handle @opts[:marshal]
223
- redis.lrange rediskey, sidx, eidx
301
+ redis.lrange(rediskey, sidx, eidx).collect do |v|
302
+ from_redis v
303
+ end
304
+ end
305
+
306
+ def members count=-1
307
+ range 0, count
308
+ end
309
+ alias_method :all, :members
310
+ alias_method :to_a, :members
311
+
312
+ #def revmembers count=1 #TODO
313
+ # range -count, 0
314
+ #end
315
+
316
+ def each &blk
317
+ range.each &blk
318
+ end
319
+
320
+ def each_with_index &blk
321
+ range.each_with_index &blk
322
+ end
323
+
324
+ def collect &blk
325
+ range.collect &blk
326
+ end
327
+
328
+ def select &blk
329
+ range.select &blk
224
330
  end
225
- alias_method :to_a, :range
226
331
 
227
332
  def at idx
228
333
  from_redis redis.lindex(rediskey, idx)
@@ -272,19 +377,39 @@ module Familia
272
377
  size == 0
273
378
  end
274
379
 
275
- def << v
276
- redis.sadd rediskey, to_redis(v)
380
+ def add *values
381
+ values.flatten.compact.each { |v| redis.sadd rediskey, to_redis(v) }
277
382
  self
278
383
  end
279
- alias_method :add, :<<
384
+
385
+ def << v
386
+ add v
387
+ end
280
388
 
281
389
  def members
282
- # TODO: handle @opts[:marshal]
283
- redis.smembers rediskey
390
+ redis.smembers(rediskey).collect do |v|
391
+ from_redis v
392
+ end
284
393
  end
285
394
  alias_method :all, :members
286
395
  alias_method :to_a, :members
287
396
 
397
+ def each &blk
398
+ members.each &blk
399
+ end
400
+
401
+ def each_with_index &blk
402
+ members.each_with_index &blk
403
+ end
404
+
405
+ def collect &blk
406
+ members.collect &blk
407
+ end
408
+
409
+ def select &blk
410
+ members.select &blk
411
+ end
412
+
288
413
  def member? v
289
414
  redis.sismember rediskey, to_redis(v)
290
415
  end
@@ -309,7 +434,7 @@ module Familia
309
434
  end
310
435
 
311
436
  def random
312
- redis.srandmember rediskey
437
+ from_redis redis.srandmember(rediskey)
313
438
  end
314
439
 
315
440
  ## Make the value stored at KEY identical to the given list
@@ -343,6 +468,10 @@ module Familia
343
468
  end
344
469
  alias_method :length, :size
345
470
 
471
+ def empty?
472
+ size == 0
473
+ end
474
+
346
475
  # NOTE: The argument order is the reverse of #add
347
476
  # e.g. obj.metrics[VALUE] = SCORE
348
477
  def []= v, score
@@ -377,29 +506,52 @@ module Familia
377
506
  ret.nil? ? nil : ret.to_i
378
507
  end
379
508
 
380
- def members opts={}
381
- range 0, -1, opts
509
+ def members count=-1, opts={}
510
+ range 0, count, opts
382
511
  end
383
512
  alias_method :to_a, :members
513
+ alias_method :all, :members
384
514
 
385
- def membersrev opts={}
386
- rangerev 0, -1, opts
515
+ def revmembers count=-1, opts={}
516
+ revrange 0, count, opts
517
+ end
518
+
519
+ def each &blk
520
+ members.each &blk
521
+ end
522
+
523
+ def each_with_index &blk
524
+ members.each_with_index &blk
525
+ end
526
+
527
+ def collect &blk
528
+ members.collect &blk
529
+ end
530
+
531
+ def select &blk
532
+ members.select &blk
387
533
  end
388
534
 
389
535
  def range sidx, eidx, opts={}
390
536
  opts[:with_scores] = true if opts[:withscores]
391
- redis.zrange rediskey, sidx, eidx, opts
537
+ redis.zrange(rediskey, sidx, eidx, opts).collect do |v|
538
+ from_redis v
539
+ end
392
540
  end
393
541
 
394
- def rangerev sidx, eidx, opts={}
542
+ def revrange sidx, eidx, opts={}
395
543
  opts[:with_scores] = true if opts[:withscores]
396
- redis.zrevrange rediskey, sidx, eidx, opts
544
+ redis.zrevrange(rediskey, sidx, eidx, opts).collect do |v|
545
+ from_redis v
546
+ end
397
547
  end
398
548
 
399
549
  # e.g. obj.metrics.rangebyscore (now-12.hours), now, :limit => [0, 10]
400
550
  def rangebyscore sscore, escore, opts={}
401
551
  opts[:with_scores] = true if opts[:withscores]
402
- redis.zrangebyscore rediskey, sscore, escore, opts
552
+ redis.zrangebyscore(rediskey, sscore, escore, opts).collect do |v|
553
+ from_redis v
554
+ end
403
555
  end
404
556
 
405
557
  def remrangebyrank srank, erank
@@ -426,7 +578,9 @@ module Familia
426
578
  redis.zrem rediskey, to_redis(v)
427
579
  end
428
580
  alias_method :remove, :delete
429
-
581
+ alias_method :rem, :delete
582
+ alias_method :del, :delete
583
+
430
584
  def at idx
431
585
  range(idx, idx).first
432
586
  end
@@ -451,14 +605,20 @@ module Familia
451
605
  end
452
606
  alias_method :length, :size
453
607
 
608
+ def empty?
609
+ size == 0
610
+ end
611
+
454
612
  def []= n, v
455
613
  redis.hset rediskey, n, to_redis(v)
456
614
  end
615
+ alias_method :put, :[]=
457
616
  alias_method :store, :[]=
458
617
 
459
618
  def [] n
460
- redis.hget rediskey, n
619
+ from_redis redis.hget(rediskey, n)
461
620
  end
621
+ alias_method :get, :[]
462
622
 
463
623
  def fetch n, default=nil
464
624
  ret = self[n]
@@ -475,10 +635,13 @@ module Familia
475
635
  end
476
636
 
477
637
  def values
478
- redis.hvals rediskey
638
+ redis.hvals(rediskey).collect do |v|
639
+ from_redis v
640
+ end
479
641
  end
480
642
 
481
643
  def all
644
+ # TODO: from_redis
482
645
  redis.hgetall rediskey
483
646
  end
484
647
  alias_method :to_hash, :all
@@ -516,7 +679,9 @@ module Familia
516
679
  alias_method :merge!, :update
517
680
 
518
681
  def values_at *names
519
- redis.hmget rediskey, *names.flatten.compact
682
+ redis.hmget(rediskey, *names.flatten.compact).collect do |v|
683
+ from_redis v
684
+ end
520
685
  end
521
686
 
522
687
  Familia::RedisObject.register self, :hash
@@ -532,10 +697,15 @@ module Familia
532
697
  end
533
698
  alias_method :length, :size
534
699
 
700
+ def empty?
701
+ size == 0
702
+ end
703
+
535
704
  def value
536
705
  redis.setnx rediskey, @opts[:default] if @opts[:default]
537
- redis.get rediskey
706
+ from_redis redis.get(rediskey)
538
707
  end
708
+ alias_method :content, :value
539
709
  alias_method :get, :value
540
710
 
541
711
  def to_s
@@ -605,3 +775,4 @@ module Familia
605
775
  end
606
776
 
607
777
  end
778
+
@@ -12,7 +12,6 @@ class Bone < Storable
12
12
  string :value, :default => "GREAT!"
13
13
  end
14
14
 
15
-
16
15
  class Session < Storable
17
16
  include Familia
18
17
  index :sessid
@@ -22,7 +21,6 @@ class Session < Storable
22
21
  ttl 60 # seconds to live
23
22
  end
24
23
 
25
-
26
24
  class Customer < Storable
27
25
  include Familia
28
26
  index :custid
data/lib/familia.rb CHANGED
@@ -22,20 +22,19 @@ module Familia
22
22
  include Gibbler::Complex
23
23
  @secret = '1-800-AWESOME' # Should be modified via Familia.secret = ''
24
24
  @apiversion = nil
25
- @uri = URI.parse 'redis://localhost'
25
+ @uri = URI.parse 'redis://127.0.0.1'
26
26
  @delim = ':'
27
27
  @clients = {}
28
28
  @classes = []
29
- @conf = {}
30
29
  @suffix = :object.freeze
31
30
  @index = :id.freeze
32
31
  @debug = false.freeze
33
32
  @dump_method = :to_json
34
33
  @load_method = :from_json
35
34
  class << self
36
- attr_reader :conf, :classes, :clients
35
+ attr_reader :classes, :clients, :uri
37
36
  attr_accessor :debug, :secret, :delim, :dump_method, :load_method
38
- attr_writer :apiversion, :uri
37
+ attr_writer :apiversion
39
38
  def debug?() @debug == true end
40
39
  def info *msg
41
40
  STDERR.puts *msg
@@ -48,49 +47,34 @@ module Familia
48
47
  info "%s (%d:%s): %s" % [label, Thread.current.object_id, redis_client.object_id, ident]
49
48
  info " +-> %s" % [context].flatten[0..3].join("\n ") if context
50
49
  end
51
- def uri(db=nil)
52
- if db.nil?
53
- @uri
54
- else
55
- uri = URI.parse @uri.to_s
56
- uri.db = db
57
- uri
58
- end
59
- end
60
- def apiversion(r=nil, &blk)
61
- if blk.nil?
62
- @apiversion = r if r;
63
- else
64
- tmp = @apiversion
65
- @apiversion = r
66
- blk.call
67
- @apiversion = tmp
68
- end
69
- @apiversion
70
- end
71
- def conf=(conf={})
72
- @conf = conf
73
- @uri = Redis.uri(@conf).freeze
74
- connect @uri
75
- @conf
50
+ def uri= v
51
+ v = URI.parse v unless URI === v
52
+ @uri = v
76
53
  end
54
+ # A convenience method for returning the appropriate Redis
55
+ # connection. If +uri+ is an Integer, we'll treat it as a
56
+ # database number. If it's a String, we'll treat it as a
57
+ # full URI (e.g. redis://1.2.3.4/15).
58
+ # Otherwise we'll return the default connection.
77
59
  def redis(uri=nil)
78
60
  if Integer === uri
79
- uri = Familia.uri(uri)
80
- else
81
- uri &&= URI.parse uri if String === uri
61
+ tmp = Familia.uri
62
+ tmp.db = uri
63
+ uri = tmp
64
+ elsif String === uri
65
+ uri &&= URI.parse uri
82
66
  end
83
67
  uri ||= Familia.uri
84
68
  connect(uri) unless @clients[uri.serverid]
85
- #STDERR.puts "REDIS: #{uri} #{caller[0]}" if Familia.debug?
86
69
  @clients[uri.serverid]
87
70
  end
88
- def connect(uri=nil, local_conf={})
71
+ def connect(uri=nil)
89
72
  uri &&= URI.parse uri if String === uri
90
73
  uri ||= Familia.uri
91
- local_conf[:thread_safe] = true
92
- client = Redis.new local_conf.merge(uri.conf)
93
- Familia.trace :CONNECT, client, uri.conf.inspect, caller.first
74
+ conf = uri.conf
75
+ conf[:thread_safe] = true
76
+ client = Redis.new conf
77
+ Familia.trace :CONNECT, client, conf.inspect, caller[0..3] if Familia.debug
94
78
  @clients[uri.serverid] = client
95
79
  end
96
80
  def reconnect_all!
@@ -107,17 +91,23 @@ module Familia
107
91
  def default_suffix=(a) @suffix = a end
108
92
  def index(r=nil) @index = r if r; @index end
109
93
  def index=(r) @index = r; r end
94
+ def join(*r) r.join(Familia.delim) end
110
95
  def split(r) r.split(Familia.delim) end
111
96
  def rediskey *args
112
97
  el = args.flatten.compact
113
98
  el.unshift @apiversion unless @apiversion.nil?
114
99
  el.join(Familia.delim)
115
100
  end
116
- def destroy keyname, uri=nil
117
- Familia.redis(uri).del keyname
118
- end
119
- def exists? keyname, uri=nil
120
- Familia.redis(uri).exists keyname
101
+ def apiversion(r=nil, &blk)
102
+ if blk.nil?
103
+ @apiversion = r if r;
104
+ else
105
+ tmp = @apiversion
106
+ @apiversion = r
107
+ blk.call
108
+ @apiversion = tmp
109
+ end
110
+ @apiversion
121
111
  end
122
112
  end
123
113
 
@@ -138,7 +128,12 @@ module Familia
138
128
  obj.send :include, Familia::InstanceMethods
139
129
  obj.send :include, Gibbler::Complex
140
130
  obj.extend Familia::ClassMethods
141
- obj.class_set :instances
131
+ obj.class_zset :instances, :class => obj, :reference => true
132
+ # :object is a special redis object because its reserved
133
+ # for storing the marshaled instance data (e.g. to_json).
134
+ # When it isn't defined explicitly we define it here b/c
135
+ # it's assumed to exist in other places (see #save).
136
+ obj.string :object, :class => obj unless obj.redis_object? :object
142
137
  Familia.classes << obj
143
138
  end
144
139
 
@@ -16,7 +16,7 @@ require 'familia/test_helpers'
16
16
  #=> ['metric0', 'metric1', 'metric2', 'metric3', 'metric4']
17
17
 
18
18
  ## Familia::SortedSet#members
19
- @a.metrics.membersrev
19
+ @a.metrics.revmembers
20
20
  #=> ['metric4', 'metric3', 'metric2', 'metric1', 'metric0']
21
21
 
22
22
  ## Familia::SortedSet#rank
@@ -28,7 +28,7 @@ Familia.apiversion = 'v1'
28
28
  ## Familia::String.new
29
29
  @ret = Familia::String.new 'arbitrary:key'
30
30
  @ret.rediskey
31
- #=> 'v1:arbitrary:key'
31
+ #=> 'arbitrary:key'
32
32
 
33
33
  ## instance set
34
34
  @ret.value = '1000'
@@ -30,8 +30,8 @@ Bone.suffix
30
30
  #=> true
31
31
 
32
32
  ## Customer.instances
33
- Customer.instances.all
34
- #=> ['delano']
33
+ Customer.instances.all.collect(&:custid)
34
+ #=> [:delano]
35
35
 
36
36
  ## Familia.from_redis
37
37
  obj = Customer.from_redis :delano