familia 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,17 @@
1
1
  FAMILIA, CHANGES
2
2
 
3
+ #### 0.6.0 (2010-12-10) ###############################
4
+
5
+ NOTE: Mucho refactoring. 0.6 syntax is not compatible with previous versions.
6
+
7
+ CHANGE: All methods name "key" are now "rediskey"
8
+ CHANGE: Familia#destroy! no longer takes a suffix argument. It now deletes
9
+ the object and all suffixes. See destroy.
10
+ CHANGE: Redis Object class methods now take the following args: name, options={}
11
+ CHANGE: Familia class suffixes are now derived from redis_objects
12
+ ADDED: Familia#destroy deletes just the object.
13
+
14
+
3
15
  #### 0.5.3 (2010-12-10) ###############################
4
16
 
5
17
  Initial public release
@@ -1,4 +1,4 @@
1
- # Familia - 0.5 BETA
1
+ # Familia - 0.6 BETA
2
2
 
3
3
  **Organize and store ruby objects in Redis**
4
4
 
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :MAJOR: 0
3
- :MINOR: 5
4
- :PATCH: 3
3
+ :MINOR: 6
4
+ :PATCH: 0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{familia}
8
- s.version = "0.5.3"
8
+ s.version = "0.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Delano Mandelbaum"]
12
- s.date = %q{2010-12-10}
12
+ s.date = %q{2010-12-17}
13
13
  s.description = %q{Organize and store ruby objects in Redis}
14
14
  s.email = %q{delano@solutious.com}
15
15
  s.extra_rdoc_files = [
@@ -23,7 +23,22 @@ Gem::Specification.new do |s|
23
23
  "Rakefile",
24
24
  "VERSION.yml",
25
25
  "familia.gemspec",
26
- "lib/familia.rb"
26
+ "lib/familia.rb",
27
+ "lib/familia/core_ext.rb",
28
+ "lib/familia/helpers.rb",
29
+ "lib/familia/object.rb",
30
+ "lib/familia/redisobject.rb",
31
+ "lib/familia/test_helpers.rb",
32
+ "lib/familia/tools.rb",
33
+ "try/00_familia.rb",
34
+ "try/10_familia_try.rb",
35
+ "try/20_redis_object_try.rb",
36
+ "try/21_redis_object_zset_try.rb",
37
+ "try/22_redis_object_set_try.rb",
38
+ "try/23_redis_object_list_try.rb",
39
+ "try/24_redis_object_string_try.rb",
40
+ "try/25_redis_object_hash_try.rb",
41
+ "try/30_familia_object_try.rb"
27
42
  ]
28
43
  s.homepage = %q{http://github.com/delano/familia}
29
44
  s.rdoc_options = ["--charset=UTF-8"]
@@ -1,6 +1,8 @@
1
1
  # encoding: utf-8
2
2
  FAMILIA_LIB_HOME = File.expand_path File.dirname(__FILE__) unless defined?(FAMILIA_LIB_HOME)
3
3
  require 'uri/redis'
4
+ require 'gibbler'
5
+ require 'familia/core_ext'
4
6
 
5
7
  module Familia
6
8
  module VERSION
@@ -16,24 +18,38 @@ module Familia
16
18
  end
17
19
  end
18
20
 
19
-
20
21
  module Familia
21
22
  include Gibbler::Complex
22
23
  @secret = '1-800-AWESOME' # Should be modified via Familia.secret = ''
24
+ @apiversion = nil
25
+ @uri = URI.parse 'redis://localhost'
26
+ @delim = ':'
23
27
  @clients = {}
28
+ @classes = []
24
29
  @conf = {}
25
30
  @suffix = :object.freeze
26
31
  @index = :id.freeze
27
- @apiversion = nil
28
- @uri = URI.parse 'redis://localhost'
29
32
  @debug = false.freeze
30
- @classes = []
31
- @delim = ':'
33
+ @dump_method = :to_json
34
+ @load_method = :from_json
32
35
  class << self
33
36
  attr_reader :conf, :classes, :clients
34
- attr_accessor :debug, :secret, :delim
37
+ attr_accessor :debug, :secret, :delim, :dump_method, :load_method
38
+ attr_writer :apiversion, :uri
35
39
  def debug?() @debug == true end
40
+ def info *msg
41
+ STDERR.puts *msg
42
+ end
43
+ def ld *msg
44
+ info *msg if debug?
45
+ end
46
+ def trace label, redis_client, ident, context=nil
47
+ return unless Familia.debug?
48
+ info "%s (%d:%s): %s" % [label, Thread.current.object_id, redis_client.object_id, ident]
49
+ info " +-> %s" % [context].flatten[0..3].join("\n ") if context
50
+ end
36
51
  end
52
+
37
53
  class Problem < RuntimeError; end
38
54
  class EmptyIndex < Problem; end
39
55
  class NonUniqueKey < Problem; end
@@ -46,870 +62,88 @@ module Familia
46
62
  "No client for #{uri.serverid}"
47
63
  end
48
64
  end
49
- def Familia.uri(db=nil)
50
- if db.nil?
51
- @uri
52
- else
53
- uri = URI.parse @uri.to_s
54
- uri.db = db
55
- uri
56
- end
57
- end
58
- def Familia.apiversion(r=nil, &blk)
59
- if blk.nil?
60
- @apiversion = r if r;
61
- else
62
- tmp = @apiversion
63
- @apiversion = r
64
- blk.call
65
- @apiversion = tmp
66
- end
67
- @apiversion
68
- end
69
- def Familia.apiversion=(r) @apiversion = r; r end
70
- def Familia.conf=(conf={})
71
- @conf = conf
72
- @uri = Redis.uri(@conf).freeze
73
- connect @uri
74
- @conf
75
- end
76
- def Familia.redis(uri=nil)
77
- uri &&= URI.parse uri if String === uri
78
- uri ||= Familia.uri
79
- connect(uri) unless @clients[uri.serverid]
80
- #STDERR.puts "REDIS: #{uri} #{caller[0]}" if Familia.debug?
81
- @clients[uri.serverid]
82
- end
83
- def Familia.connect(uri=nil, local_conf={})
84
- uri &&= URI.parse uri if String === uri
85
- uri ||= Familia.uri
86
- local_conf[:thread_safe] = true
87
- client = Redis.new local_conf.merge(uri.conf)
88
- Familia.trace :CONNECT, client, uri.conf.inspect, caller.first
89
- @clients[uri.serverid] = client
90
- end
91
- def Familia.reconnect_all!
92
- Familia.classes.each do |klass|
93
- klass.redis.client.reconnect
94
- Familia.info "#{klass} ping: #{klass.redis.ping}" if debug?
95
- end
96
- end
97
- def Familia.connected?(uri=nil)
98
- uri &&= URI.parse uri if String === uri
99
- @clients.has_key?(uri.serverid)
100
- end
101
- def Familia.default_suffix(a=nil) @suffix = a if a; @suffix end
102
- def Familia.default_suffix=(a) @suffix = a end
103
- def Familia.index(r=nil) @index = r if r; @index end
104
- def Familia.index=(r) @index = r; r end
105
- def Familia.split(r) r.split(Familia.delim) end
106
- def Familia.key *args
107
- el = args.flatten.compact
108
- el.unshift @apiversion unless @apiversion.nil?
109
- el.join(Familia.delim)
110
- end
111
- def Familia.info *msg
112
- STDERR.puts *msg
113
- end
114
- def Familia.ld *msg
115
- info *msg if debug?
116
- end
117
- def Familia.trace label, redis_client, ident, context=nil
118
- return unless Familia.debug?
119
- info "%s (%d:%s): %s" % [label, Thread.current.object_id, redis_client.object_id, ident]
120
- info " +-> %s" % [context].flatten[0..3].join("\n ") if context
121
- end
122
- def Familia.destroy keyname, uri=nil
123
- Familia.redis(uri).del keyname
124
- end
125
- def Familia.get_any keyname, uri=nil
126
- type = Familia.redis(uri).type keyname
127
- case type
128
- when "string"
129
- Familia.redis(uri).get keyname
130
- when "list"
131
- Familia.redis(uri).lrange(keyname, 0, -1) || []
132
- when "set"
133
- Familia.redis(uri).smembers( keyname) || []
134
- when "zset"
135
- Familia.redis(uri).zrange(keyname, 0, -1) || []
136
- when "hash"
137
- Familia.redis(uri).hgetall(keyname) || {}
138
- else
139
- nil
140
- end
141
- end
142
- def Familia.exists?(keyname, uri=nil)
143
- Familia.redis(uri).exists keyname
144
- end
145
-
146
- def self.included(obj)
147
- obj.send :include, Familia::InstanceMethods
148
- obj.send :include, Gibbler::Complex
149
- obj.extend Familia::ClassMethods
150
- Familia.classes << obj
151
- end
152
-
153
- module InstanceMethods
154
- def redisinfo
155
- info = {
156
- :db => self.class.db || 0,
157
- #:uri => redisuri,
158
- :key => key,
159
- :type => redistype,
160
- :ttl => realttl
161
- }
162
- end
163
- def exists?
164
- Familia.redis(self.class.uri).exists self.key
165
- end
166
- def destroy!(suffix=nil)
167
- ret = Familia.redis(self.class.uri).del self.key(suffix)
168
- Familia.trace :DELETED, Familia.redis(self.class.uri), "#{key(suffix)}: #{ret}", caller.first
169
- ret
170
- end
171
- def allkeys
172
- keynames = [key]
173
- self.class.suffixes.each do |sfx|
174
- keynames << key(sfx)
175
- end
176
- keynames
177
- end
178
- def key(suffix=nil)
179
- raise EmptyIndex, self.class if index.nil? || index.empty?
180
- if suffix.nil?
181
- suffix = self.class.suffix.kind_of?(Proc) ?
182
- self.class.suffix.call(self) :
183
- self.class.suffix
184
- end
185
- self.class.key self.index, suffix
186
- end
187
- def save(force=false)
188
- Familia.trace :SAVE, Familia.redis(self.class.uri), redisuri, caller.first
189
- ## Don't save if there are no changes
190
- ##return false unless force || self.gibbled? || self.gibbler_cache.nil?
191
- preprocess if respond_to?(:preprocess)
192
- self.update_time if self.respond_to?(:update_time)
193
- ret = Familia.redis(self.class.uri).set self.key, self.to_json
194
- unless self.ttl.nil? || self.ttl <= 0
195
- Familia.trace :SET_EXPIRE, Familia.redis(self.class.uri), "#{self.key} to #{self.ttl}"
196
- expire(self.ttl)
197
- end
198
- ret == "OK"
199
- end
200
- def index
201
- if @index.nil?
202
- self.class.index.kind_of?(Proc) ?
203
- self.class.index.call(self) :
204
- self.send(self.class.index)
205
- else
206
- @index
207
- end
208
- end
209
- def index=(i)
210
- @index = i
211
- end
212
- def expire(ttl=nil)
213
- ttl ||= self.class.ttl
214
- Familia.redis(self.class.uri).expire self.key, ttl.to_i
215
- end
216
- def realttl
217
- Familia.redis(self.class.uri).ttl self.key
218
- end
219
- def ttl=(v)
220
- @ttl = v.to_i
221
- end
222
- def ttl
223
- @ttl || self.class.ttl
224
- end
225
- def raw(suffix=nil)
226
- suffix ||= :object
227
- Familia.redis(self.class.uri).get key(suffix)
228
- end
229
- def redisuri(suffix=nil)
230
- u = URI.parse self.class.uri.to_s
231
- u.db ||= self.class.db.to_s
232
- u.key = key(suffix)
233
- u
234
- end
235
- def redistype(suffix=nil)
236
- Familia.redis(self.class.uri).type key(suffix)
237
- end
238
- # Finds the shortest available unique key (lower limit of 6)
239
- def shortid
240
- len = 6
241
- loop do
242
- begin
243
- self.class.expand(@id.shorten(len))
244
- break
245
- rescue Familia::NonUniqueKey
246
- len += 1
247
- end
248
- end
249
- @id.shorten(len)
250
- end
251
- end
252
65
 
253
66
  module ClassMethods
254
- def inherited(obj)
255
- obj.db = self.db
256
- Familia.classes << obj
257
- super(obj)
258
- end
259
- def from_redisdump dump
260
- dump
261
- end
262
- def float
263
- Proc.new do |v|
264
- v.nil? ? 0 : v.to_f
265
- end
266
- end
267
- def extended(obj)
268
- obj.db = self.db
269
- Familia.classes << obj
270
- end
271
- def db(db=nil)
272
- @db = db if db;
273
- @db
274
- end
275
- def db=(db) @db = db end
276
- def host(host=nil) @host = host if host; @host end
277
- def host=(host) @host = host end
278
- def port(port=nil) @port = port if port; @port end
279
- def port=(port) @port = port end
280
- def uri=(uri)
281
- uri = URI.parse uri if String === uri
282
- @uri = uri
283
- end
284
- def uri(uri=nil)
285
- self.uri = uri unless uri.to_s.empty?
286
- return @uri if @uri
287
- @uri = URI.parse Familia.uri.to_s
288
- @uri.db = @db if @db
289
- Familia.connect @uri #unless Familia.connected?(@uri)
290
- @uri
291
- end
292
- def redis
293
- Familia.redis(self.uri)
294
- end
295
- def flushdb
296
- Familia.info "flushing #{uri}"
297
- redis.flushdb
298
- end
299
- def keys(suffix=nil)
300
- self.redis.keys(key('*',suffix)) || []
301
- end
302
- def all(suffix=nil)
303
- # objects that could not be parsed will be nil
304
- keys(suffix).collect { |k| from_key(k) }.compact
305
- end
306
- def any?(filter='*')
307
- size(filter) > 0
308
- end
309
- def size(filter='*')
310
- self.redis.keys(key(filter)).compact.size
311
- end
312
- def suffix=(val)
313
- suffixes << (@suffix = val)
314
- val
315
- end
316
- def suffix(a=nil, &blk)
317
- @suffix = a || blk if a || !blk.nil?
318
- val = @suffix || Familia.default_suffix
319
- self.suffixes << val
320
- val
321
- end
322
- def prefix=(a) @prefix = a end
323
- def prefix(a=nil) @prefix = a if a; @prefix || self.name.downcase end
324
- def index(i=nil, &blk)
325
- @index = i || blk if i || !blk.nil?
326
- @index ||= Familia.index
327
- @index
328
- end
329
- def suffixes
330
- @suffixes ||= []
331
- @suffixes.uniq!
332
- @suffixes
333
- end
334
- def child(opts={})
335
- name, klass = opts.keys.first, opts.values.first
336
- childs[name] = klass
337
- self.suffixes << name
338
- define_method :"#{name}_key" do
339
- key(name)
340
- end
341
- define_method :"#{name}?" do
342
- #Familia.ld "EXISTS? #{self.class.childs[name]} #{key(name)}"
343
- self.class.childs[name].redis.exists key(name)
344
- end
345
- define_method :"clear_#{name}" do
346
- self.class.redis.del key(name)
347
- end
348
- define_method :"#{name}" do
349
- #Familia.ld "#{self.class} Return child #{key(name)}"
350
- content = self.class.redis.get key(name)
351
- #Familia.ld "TODO: don't reload #{self.class} every time"
352
- if !content.nil?
353
- begin
354
- content = self.class.childs[name].from_json content if klass != String && content.is_a?(String)
355
- rescue => ex
356
- msg = "Error loading #{name} for #{key}: #{ex.message}"
357
- Familia.info "#{msg}: #{$/}#{content}"
358
- raise Familia::Problem, msg
359
- end
360
- else
361
- content = self.class.childs[name].new
362
- end
363
- content
364
- end
365
- define_method :"#{name}=" do |content|
366
- Familia.ld "#{self.class} Modify child #{key(name)} (#{content.class})"
367
- self.class.redis.set key(name), (content.is_a?(String) ? content : content.to_json)
368
- content
369
- end
370
- end
371
- def child?(name)
372
- childs.has_key? :"#{name}"
373
- end
374
- def childs
375
- @childs ||= {}
376
- @childs
377
- end
378
- def hashes
379
- @hashes ||= {}
380
- @hashes
381
- end
382
- def hash?(name)
383
- @hashes.has_key? :"#{name}"
384
- end
385
- def hash(opts={}, &blk)
386
- if Hash === opts
387
- name, klass = opts.keys.first, opts.values.first
388
- else
389
- name, klass = opts, nil
390
- end
391
- hashes[name] = klass
392
- self.suffixes << name
393
- if name.to_s.match(/s$/i)
394
- name_plural = name.to_s.clone
395
- name_singular = name.to_s[0..-2]
67
+ def uri(db=nil)
68
+ if db.nil?
69
+ @uri
396
70
  else
397
- name_plural = "#{name}s"
398
- name_singular = name
399
- end
400
- define_method :"#{name}_key" do
401
- key(name)
402
- end
403
- define_method :"has_#{name}?" do |field|
404
- self.class.redis.hexists key(name), field
405
- end
406
- define_method :"#{name}_size" do
407
- self.class.redis.hlen key(name)
408
- end
409
- define_method :"clear_#{name}" do
410
- self.class.redis.del key(name)
411
- end
412
- define_method :"#{name}_keys" do
413
- self.class.redis.hkeys key(name)
414
- end
415
- define_method :"set_#{name}" do |hash|
416
- self.class.redis.hmset key(name), *hash.to_a.flatten
417
- end
418
- define_method :"get_#{name}" do |*fields|
419
- ret = self.class.redis.hmget key(name), *fields
420
- ret.collect! { |obj| blk.call(obj) } if blk
421
- fields.size == 1 ? ret.first : ret
422
- end
423
- define_method :"del_#{name}" do |field|
424
- self.class.redis.hdel key(name), field
425
- end
426
- end
427
-
428
- def sets
429
- @sets ||= {}
430
- @sets
431
- end
432
- def set?(name)
433
- sets.has_key? :"#{name}"
434
- end
435
- def set(opts={})
436
- if Hash === opts
437
- name, klass = opts.keys.first, opts.values.first
438
- else
439
- name, klass = opts, nil
440
- end
441
- sets[name] = klass
442
- self.suffixes << name
443
- if name.to_s.match(/s$/i)
444
- name_plural = name.to_s.clone
445
- name_singular = name.to_s[0..-2]
446
- else
447
- name_plural = "#{name}s"
448
- name_singular = name
449
- end
450
- define_method :"#{name}_key" do
451
- key(name)
452
- end
453
- define_method :"#{name}_size" do
454
- self.class.redis.scard key(name)
455
- end
456
- # Make the value stored at KEY identical to the given list
457
- define_method :"#{name}_sync" do |*latest|
458
- latest = latest.flatten.compact
459
- # Do nothing if we're given an empty Array.
460
- # Otherwise this would clear all current values
461
- if latest.empty?
462
- false
463
- else
464
- # Convert to a list of index values if we got the actual objects
465
- latest = latest.collect { |obj| obj.index } if klass === latest.first
466
- current = send("#{name_plural}raw")
467
- added = latest-current
468
- removed = current-latest
469
- #Familia.info "#{self.index}: adding: #{added}"
470
- added.each { |v| self.send("add_#{name_singular}", v) }
471
- #Familia.info "#{self.index}: removing: #{removed}"
472
- removed.each { |v| self.send("remove_#{name_singular}", v) }
473
- true
474
- end
475
- end
476
- define_method :"#{name}?" do
477
- self.send(:"#{name}_size") > 0
478
- end
479
- define_method :"clear_#{name}" do
480
- self.class.redis.del key(name)
481
- end
482
- define_method :"add_#{name_singular}" do |obj|
483
- objid = klass === obj ? obj.index : obj
484
- #Familia.ld "#{self.class} Add #{objid} to #{key(name)}"
485
- self.class.redis.sadd key(name), objid
486
- end
487
- define_method :"remove_#{name_singular}" do |obj|
488
- objid = klass === obj ? obj.index : obj
489
- #Familia.ld "#{self.class} Remove #{objid} from #{key(name)}"
490
- self.class.redis.srem key(name), objid
491
- end
492
- # Example:
493
- #
494
- # list = obj.response_time 10, :score => (now-12.hours)..now
495
- #
496
- define_method :"#{name_plural}raw" do
497
- list = self.class.redis.smembers(key(name)) || []
498
- end
499
- define_method :"#{name_plural}" do
500
- list = send("#{name_plural}raw")
501
- if klass.nil?
502
- list
503
- elsif klass.include?(Familia)
504
- klass.multiget(*list)
505
- elsif klass.respond_to?(:from_json)
506
- list.collect { |str| klass.from_json(str) }
507
- else
508
- list
509
- end
510
- end
511
- end
512
- def zsets
513
- @zsets ||= {}
514
- @zsets
515
- end
516
- def zset?(name)
517
- zsets.has_key? :"#{name}"
518
- end
519
- def zset(opts={})
520
- if Hash === opts
521
- name, klass = opts.keys.first, opts.values.first
522
- else
523
- name, klass = opts, nil
524
- end
525
- zsets[name] = klass
526
- self.suffixes << name
527
- if name.to_s.match(/s$/i)
528
- name_plural = name.to_s.clone
529
- name_singular = name.to_s[0..-2]
530
- else
531
- name_plural = "#{name}s"
532
- name_singular = name
533
- end
534
- define_method :"#{name}_key" do
535
- key(name)
536
- end
537
- define_method :"#{name}_size" do
538
- self.class.redis.zcard key(name)
539
- end
540
- define_method :"clear_#{name}" do
541
- self.class.redis.del key(name)
542
- end
543
- define_method :"#{name}?" do
544
- self.send(:"#{name}_size") > 0
545
- end
546
- define_method :"add_#{name_singular}" do |score,obj|
547
- objid = klass === obj ? obj.index : obj
548
- #Familia.ld "#{self.class} Add #{objid} (#{score}) to #{key(name)}"
549
- self.class.redis.zadd key(name), score, objid
550
- end
551
- #p "Adding: #{self}#remove_#{name_singular}"
552
- define_method :"remove_#{name_singular}" do |obj|
553
- objid = klass === obj ? obj.index : obj
554
- #Familia.ld "#{self.class} Remove #{objid} from #{key(name)}"
555
- self.class.redis.zrem key(name), objid
556
- end
557
- # Example:
558
- #
559
- # list = obj.response_time 10, :score => (now-12.hours)..now
560
- #
561
- define_method :"#{name_plural}raw" do |*args|
562
-
563
- count = args.first-1 unless args.empty?
564
- count ||= -1
565
-
566
- opts = args[1] || {}
567
- if Range === opts[:score]
568
- lo, hi = opts[:score].first, opts[:score].last
569
- list = self.class.redis.zrangebyscore(key(name), lo, hi, :limit => [0, count]) || []
570
- else
571
- list = self.class.redis.zrange(key(name), 0, count) || []
572
- end
573
- end
574
- define_method :"#{name_plural}" do |*args|
575
- list = send("#{name_plural}raw", *args)
576
- if klass.nil?
577
- list
578
- elsif klass.include?(Familia)
579
- klass.multiget(*list)
580
- elsif klass.respond_to?(:from_json)
581
- list.collect { |str| klass.from_json(str) }
582
- else
583
- list
584
- end
585
- end
586
- define_method :"#{name_plural}rev" do |*args|
587
-
588
- count = args.first-1 unless args.empty?
589
- count ||= -1
590
-
591
- opts = args[1] || {}
592
- if Range === opts[:score]
593
- lo, hi = opts[:score].first, opts[:score].last
594
- list = self.class.redis.zrangebyscore(key(name), lo, hi, :limit => [0, count]) || []
595
- else
596
- list = self.class.redis.zrevrange(key(name), 0, count) || []
597
- end
598
- if klass.nil?
599
- list
600
- elsif klass.include?(Familia)
601
- klass.multiget(*list)
602
- elsif klass.respond_to?(:from_json)
603
- list.collect { |str| klass.from_json(str) }
604
- else
605
- list
606
- end
607
- end
608
- end
609
-
610
- def list(opts={})
611
- if Hash === opts
612
- name, klass = opts.keys.first, opts.values.first
613
- else
614
- name, klass = opts, nil
615
- end
616
- lists[name] = klass
617
- self.suffixes << name
618
- if name.to_s.match(/s$/i)
619
- name_plural = name.to_s.clone
620
- name_singular = name.to_s[0..-2]
621
- else
622
- name_plural = "#{name}s"
623
- name_singular = name
624
- end
625
- define_method :"#{name}_key" do
626
- key(name)
627
- end
628
- define_method :"#{name}_size" do
629
- self.class.redis.llen key(name)
630
- end
631
- define_method :"clear_#{name}" do
632
- self.class.redis.del key(name)
633
- end
634
- # Make the value stored at KEY identical to the given list
635
- define_method :"#{name}_sync" do |*latest|
636
- latest = latest.flatten.compact
637
- # Do nothing if we're given an empty Array.
638
- # Otherwise this would clear all current values
639
- if latest.empty?
640
- false
641
- else
642
- # Convert to a list of index values if we got the actual objects
643
- latest = latest.collect { |obj| obj.index } if klass === latest.first
644
- current = send("#{name_plural}raw")
645
- added = latest-current
646
- removed = current-latest
647
- #Familia.info "#{self.index}: adding: #{added}"
648
- added.each { |v| self.send("add_#{name_singular}", v) }
649
- #Familia.info "#{self.index}: removing: #{removed}"
650
- removed.each { |v| self.send("remove_#{name_singular}", v) }
651
- true
652
- end
653
- end
654
- define_method :"#{name}?" do
655
- self.send(:"#{name}_size") > 0
656
- end
657
- define_method :"add_#{name_singular}" do |obj|
658
- objid = klass === obj ? obj.index : obj
659
- #Familia.ld "#{self.class} Add #{objid} to #{key(name)}"
660
- ret = self.class.redis.rpush key(name), objid
661
- # TODO : copy to zset and set
662
- #unless self.ttl.nil? || self.ttl <= 0
663
- # Familia.trace :SET_EXPIRE, Familia.redis(self.class.uri), "#{self.key} to #{self.ttl}"
664
- # Familia.redis(self.class.uri).expire key(name), self.ttl
665
- #end
666
- ret
667
- end
668
- define_method :"remove_#{name_singular}" do |obj|
669
- objid = klass === obj ? obj.index : obj
670
- #Familia.ld "#{self.class} Remove #{objid} from #{key(name)}"
671
- self.class.redis.lrem key(name), 0, objid
672
- end
673
- define_method :"#{name_plural}raw" do |*args|
674
- count = args.first-1 unless args.empty?
675
- count ||= -1
676
- list = self.class.redis.lrange(key(name), 0, count) || []
677
- end
678
- define_method :"#{name_plural}" do |*args|
679
- list = send("#{name_plural}raw", *args)
680
- if klass.nil?
681
- list
682
- elsif klass.include?(Familia)
683
- klass.multiget(*list)
684
- elsif klass.respond_to?(:from_json)
685
- list.collect { |str| klass.from_json(str) }
686
- else
687
- list
688
- end
689
- end
690
- end
691
- def lists
692
- @lists ||= {}
693
- @lists
694
- end
695
- def list?(name)
696
- lists.has_key? :"#{name}"
697
- end
698
- def multiget(*ids)
699
- ids = rawmultiget(*ids)
700
- ids.compact.collect { |json| self.from_json(json) }.compact
701
- end
702
- def rawmultiget(*ids)
703
- ids.collect! { |objid| self.key(objid) }
704
- return [] if ids.compact.empty?
705
- Familia.trace :MULTIGET, self.redis, "#{ids.size}: #{ids}", caller
706
- ids = self.redis.mget *ids
707
- end
708
- def ttl(sec=nil)
709
- @ttl = sec.to_i unless sec.nil?
710
- @ttl
711
- end
712
- def create(*args)
713
- me = new(*args)
714
- raise "#{self} exists: #{me.to_json}" if me.exists?
715
- me.save
716
- me
717
- end
718
- def load_or_create(id)
719
- if exists?(id)
720
- from_redis(id)
721
- else
722
- me = new id
723
- me.save
724
- me
71
+ uri = URI.parse @uri.to_s
72
+ uri.db = db
73
+ uri
725
74
  end
726
75
  end
727
- def from_key(akey)
728
- Familia.trace :LOAD, Familia.redis(self.uri), "#{self.uri}/#{akey}", caller
729
- return nil unless Familia.redis(self.uri).exists akey
730
- raise Familia::Problem, "Null key" if akey.nil? || akey.empty?
731
- run_json = Familia.redis(self.uri).get akey
732
- if run_json.nil? || run_json.empty?
733
- Familia.info "No content @ #{akey}"
734
- return
735
- end
736
- begin
737
- #run_json.force_encoding("ASCII-8BIT") if RUBY_VERSION >= "1.9"
738
- obj = self.from_json(run_json)
739
- obj
740
- rescue => ex
741
- STDOUT.puts "Non-fatal error parsing JSON for #{akey}: #{ex.message}"
742
- STDOUT.puts run_json
743
- STDERR.puts ex.backtrace
744
- nil
745
- end
746
- end
747
- def from_redis(objid, suffix=nil)
748
- objid &&= objid.to_s
749
- return nil if objid.nil? || objid.empty?
750
- this_key = key(objid, suffix)
751
- #Familia.ld "Reading key: #{this_key}"
752
- me = from_key(this_key)
753
- me.gibbler # prime the gibbler cache (used to check for changes)
754
- me
755
- end
756
- def exists?(objid, suffix=nil)
757
- objid &&= objid.to_s
758
- return false if objid.nil? || objid.empty?
759
- ret = Familia.redis(self.uri).exists key(objid, suffix)
760
- Familia.trace :EXISTS, Familia.redis(self.uri), "#{key(objid)} #{ret}", caller.first
761
- ret
762
- end
763
- def destroy!(runid, suffix=nil)
764
- ret = Familia.redis(self.uri).del key(runid, suffix)
765
- Familia.trace :DELETED, Familia.redis(self.uri), "#{key(runid)}: #{ret}", caller.first
766
- ret
767
- end
768
- def find(suffix='*')
769
- list = Familia.redis(self.uri).keys(key('*', suffix)) || []
770
- end
771
- def key(runid, suffix=nil)
772
- suffix ||= self.suffix
773
- runid ||= ''
774
- runid &&= runid.to_s
775
- str = Familia.key(prefix, runid, suffix)
776
- str
777
- end
778
- def expand(short_key, suffix=nil)
779
- suffix ||= self.suffix
780
- expand_key = Familia.key(self.prefix, "#{short_key}*", suffix)
781
- Familia.trace :EXPAND, Familia.redis(self.uri), expand_key, caller.first
782
- list = Familia.redis(self.uri).keys expand_key
783
- case list.size
784
- when 0
785
- nil
786
- when 1
787
- matches = list.first.match(/\A#{Familia.key(prefix)}\:(.+?)\:#{suffix}/) || []
788
- matches[1]
76
+ def apiversion(r=nil, &blk)
77
+ if blk.nil?
78
+ @apiversion = r if r;
789
79
  else
790
- raise Familia::NonUniqueKey, "Short key returned more than 1 match"
791
- end
80
+ tmp = @apiversion
81
+ @apiversion = r
82
+ blk.call
83
+ @apiversion = tmp
84
+ end
85
+ @apiversion
86
+ end
87
+ def conf=(conf={})
88
+ @conf = conf
89
+ @uri = Redis.uri(@conf).freeze
90
+ connect @uri
91
+ @conf
92
+ end
93
+ def redis(uri=nil)
94
+ uri &&= URI.parse uri if String === uri
95
+ uri ||= Familia.uri
96
+ connect(uri) unless @clients[uri.serverid]
97
+ #STDERR.puts "REDIS: #{uri} #{caller[0]}" if Familia.debug?
98
+ @clients[uri.serverid]
99
+ end
100
+ def connect(uri=nil, local_conf={})
101
+ uri &&= URI.parse uri if String === uri
102
+ uri ||= Familia.uri
103
+ local_conf[:thread_safe] = true
104
+ client = Redis.new local_conf.merge(uri.conf)
105
+ Familia.trace :CONNECT, client, uri.conf.inspect, caller.first
106
+ @clients[uri.serverid] = client
107
+ end
108
+ def reconnect_all!
109
+ Familia.classes.each do |klass|
110
+ klass.redis.client.reconnect
111
+ Familia.info "#{klass} ping: #{klass.redis.ping}" if debug?
112
+ end
113
+ end
114
+ def connected?(uri=nil)
115
+ uri &&= URI.parse uri if String === uri
116
+ @clients.has_key?(uri.serverid)
117
+ end
118
+ def default_suffix(a=nil) @suffix = a if a; @suffix end
119
+ def default_suffix=(a) @suffix = a end
120
+ def index(r=nil) @index = r if r; @index end
121
+ def index=(r) @index = r; r end
122
+ def split(r) r.split(Familia.delim) end
123
+ def rediskey *args
124
+ el = args.flatten.compact
125
+ el.unshift @apiversion unless @apiversion.nil?
126
+ el.join(Familia.delim)
127
+ end
128
+ def destroy keyname, uri=nil
129
+ Familia.redis(uri).del keyname
130
+ end
131
+ def exists? keyname, uri=nil
132
+ Familia.redis(uri).exists keyname
792
133
  end
793
134
  end
794
- end
795
-
796
- module Familia
797
- #
798
- # class Example
799
- # include Familia
800
- # field :name
801
- # include Familia::Stamps
802
- # end
803
- #
804
- module Stamps
805
- def self.included(obj)
806
- obj.module_eval do
807
- field :created => Integer
808
- field :updated => Integer
809
- def init_stamps
810
- now = Time.now.utc.to_i
811
- @created ||= now
812
- @updated ||= now
813
- end
814
- def created
815
- @created ||= Time.now.utc.to_i
816
- end
817
- def updated
818
- @updated ||= Time.now.utc.to_i
819
- end
820
- def created_age
821
- Time.now.utc.to_i-created
822
- end
823
- def updated_age
824
- Time.now.utc.to_i-updated
825
- end
826
- def update_time
827
- @updated = Time.now.utc.to_i
828
- end
829
- def update_time!
830
- update_time
831
- save if respond_to? :save
832
- @updated
833
- end
834
- end
835
- end
836
- end
837
- module Status
838
- def self.included(obj)
839
- obj.module_eval do
840
- field :status
841
- field :message
842
- def failure?() status? 'failure' end
843
- def success?() status? 'success' end
844
- def pending?() status? 'pending' end
845
- def expired?() status? 'expired' end
846
- def disabled?() status? 'disabled' end
847
- def failure!(msg=nil) status! 'failure', msg end
848
- def success!(msg=nil) status! 'success', msg end
849
- def pending!(msg=nil) status! 'pending', msg end
850
- def expired!(msg=nil) status! 'expired', msg end
851
- def disabled!(msg=nil) status! 'disabled', msg end
852
- private
853
- def status?(s)
854
- status.to_s == s.to_s
855
- end
856
- def status!(s, msg=nil)
857
- @updated = Time.now.utc.to_f
858
- @status, @message = s, msg
859
- save if respond_to? :save
860
- end
861
- end
862
- end
135
+
136
+ def self.included(obj)
137
+ obj.send :include, Familia::Object::InstanceMethods
138
+ obj.send :include, Gibbler::Complex
139
+ obj.extend Familia::Object::ClassMethods
140
+ Familia.classes << obj
863
141
  end
864
- end
142
+
143
+ require 'familia/object'
144
+ require 'familia/helpers'
865
145
 
866
- module Familia
867
- module Tools
868
- extend self
869
- def move_keys(filter, source_uri, target_uri, &each_key)
870
- if target_uri == source_uri
871
- raise "Source and target are the same (#{target_uri})"
872
- end
873
- Familia.connect target_uri
874
- source_keys = Familia.redis(source_uri).keys(filter)
875
- puts "Moving #{source_keys.size} keys from #{source_uri} to #{target_uri} (filter: #{filter})"
876
- source_keys.each_with_index do |key,idx|
877
- type = Familia.redis(source_uri).type key
878
- ttl = Familia.redis(source_uri).ttl key
879
- if source_uri.host == target_uri.host && source_uri.port == target_uri.port
880
- Familia.redis(source_uri).move key, target_uri.db
881
- else
882
- case type
883
- when "string"
884
- value = Familia.redis(source_uri).get key
885
- when "list"
886
- value = Familia.redis(source_uri).lrange key, 0, -1
887
- when "set"
888
- value = Familia.redis(source_uri).smembers key
889
- else
890
- raise Familia::Problem, "unknown key type: #{type}"
891
- end
892
- raise "Not implemented"
893
- end
894
- each_key.call(idx, type, key, ttl) unless each_key.nil?
895
- end
896
- end
897
- # Use the return value from each_key as the new key name
898
- def rename(filter, source_uri, target_uri=nil, &each_key)
899
- target_uri ||= source_uri
900
- move_keys filter, source_uri, target_uri if source_uri != target_uri
901
- source_keys = Familia.redis(source_uri).keys(filter)
902
- puts "Renaming #{source_keys.size} keys from #{source_uri} (filter: #{filter})"
903
- source_keys.each_with_index do |key,idx|
904
- Familia.trace :RENAME1, Familia.redis(source_uri), "#{key}", ''
905
- type = Familia.redis(source_uri).type key
906
- ttl = Familia.redis(source_uri).ttl key
907
- newkey = each_key.call(idx, type, key, ttl) unless each_key.nil?
908
- Familia.trace :RENAME2, Familia.redis(source_uri), "#{key} -> #{newkey}", caller[0]
909
- ret = Familia.redis(source_uri).renamenx key, newkey
910
- end
911
- end
912
- end
146
+ extend Familia::ClassMethods
913
147
  end
914
148
 
915
149
 
@@ -924,12 +158,3 @@ module Familia
924
158
  end
925
159
  end
926
160
  end
927
-
928
- class Symbol
929
- unless method_defined?(:to_proc)
930
- def to_proc
931
- proc { |obj, *args| obj.send(self, *args) }
932
- end
933
- end
934
- end
935
-