familia 0.10.2 → 1.0.0.pre.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.pre-commit-config.yaml +1 -1
  4. data/.rubocop.yml +75 -0
  5. data/.rubocop_todo.yml +63 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +47 -15
  8. data/README.md +65 -13
  9. data/VERSION.yml +4 -3
  10. data/familia.gemspec +18 -13
  11. data/lib/familia/base.rb +33 -0
  12. data/lib/familia/connection.rb +87 -0
  13. data/lib/familia/core_ext.rb +119 -124
  14. data/lib/familia/errors.rb +33 -0
  15. data/lib/familia/features/api_version.rb +19 -0
  16. data/lib/familia/features/atomic_saves.rb +8 -0
  17. data/lib/familia/features/quantizer.rb +35 -0
  18. data/lib/familia/features/safe_dump.rb +194 -0
  19. data/lib/familia/features.rb +51 -0
  20. data/lib/familia/horreum/class_methods.rb +292 -0
  21. data/lib/familia/horreum/commands.rb +106 -0
  22. data/lib/familia/horreum/relations_management.rb +141 -0
  23. data/lib/familia/horreum/serialization.rb +193 -0
  24. data/lib/familia/horreum/settings.rb +63 -0
  25. data/lib/familia/horreum/utils.rb +44 -0
  26. data/lib/familia/horreum.rb +248 -0
  27. data/lib/familia/logging.rb +232 -0
  28. data/lib/familia/redistype/commands.rb +56 -0
  29. data/lib/familia/redistype/serialization.rb +110 -0
  30. data/lib/familia/redistype.rb +185 -0
  31. data/lib/familia/refinements.rb +88 -0
  32. data/lib/familia/settings.rb +38 -0
  33. data/lib/familia/types/hashkey.rb +107 -0
  34. data/lib/familia/types/list.rb +155 -0
  35. data/lib/familia/types/sorted_set.rb +234 -0
  36. data/lib/familia/types/string.rb +115 -0
  37. data/lib/familia/types/unsorted_set.rb +123 -0
  38. data/lib/familia/utils.rb +125 -0
  39. data/lib/familia/version.rb +25 -0
  40. data/lib/familia.rb +57 -161
  41. data/lib/redis_middleware.rb +109 -0
  42. data/try/00_familia_try.rb +5 -4
  43. data/try/10_familia_try.rb +21 -17
  44. data/try/20_redis_type_try.rb +67 -0
  45. data/try/{21_redis_object_zset_try.rb → 21_redis_type_zset_try.rb} +2 -2
  46. data/try/{22_redis_object_set_try.rb → 22_redis_type_set_try.rb} +2 -2
  47. data/try/{23_redis_object_list_try.rb → 23_redis_type_list_try.rb} +2 -2
  48. data/try/{24_redis_object_string_try.rb → 24_redis_type_string_try.rb} +6 -6
  49. data/try/{25_redis_object_hash_try.rb → 25_redis_type_hash_try.rb} +3 -3
  50. data/try/26_redis_bool_try.rb +10 -6
  51. data/try/27_redis_horreum_try.rb +93 -0
  52. data/try/30_familia_object_try.rb +21 -20
  53. data/try/35_feature_safedump_try.rb +83 -0
  54. data/try/40_customer_try.rb +140 -0
  55. data/try/41_customer_safedump_try.rb +86 -0
  56. data/try/test_helpers.rb +194 -0
  57. metadata +51 -47
  58. data/lib/familia/helpers.rb +0 -70
  59. data/lib/familia/object.rb +0 -533
  60. data/lib/familia/redisobject.rb +0 -1017
  61. data/lib/familia/test_helpers.rb +0 -40
  62. data/lib/familia/tools.rb +0 -67
  63. data/try/20_redis_object_try.rb +0 -44
@@ -1,533 +0,0 @@
1
- require 'ostruct'
2
-
3
- module Familia
4
- require 'familia/redisobject'
5
-
6
- # Auto-extended into a class that includes Familia
7
- module ClassMethods
8
-
9
- Familia::RedisObject.registration.each_pair do |kind, klass|
10
- # e.g.
11
- #
12
- # list(name, klass, opts)
13
- # list?(name)
14
- # lists
15
- #
16
- define_method :"#{kind}" do |*args, &blk|
17
- name, opts = *args
18
- install_redis_object name, klass, opts
19
- redis_objects[name.to_s.to_sym]
20
- end
21
- define_method :"#{kind}?" do |name|
22
- obj = redis_objects[name.to_s.to_sym]
23
- !obj.nil? && klass == obj.klass
24
- end
25
- define_method :"#{kind}s" do
26
- names = redis_objects_order.select { |name| send(:"#{kind}?", name) }
27
- names.collect! { |name| redis_objects[name] }
28
- names
29
- end
30
- # e.g.
31
- #
32
- # class_list(name, klass, opts)
33
- # class_list?(name)
34
- # class_lists
35
- #
36
- define_method :"class_#{kind}" do |*args, &blk|
37
- name, opts = *args
38
- install_class_redis_object name, klass, opts
39
- end
40
- define_method :"class_#{kind}?" do |name|
41
- obj = class_redis_objects[name.to_s.to_sym]
42
- !obj.nil? && klass == obj.klass
43
- end
44
- define_method :"class_#{kind}s" do
45
- names = class_redis_objects_order.select { |name| ret = send(:"class_#{kind}?", name) }
46
- # TODO: This returns instances of the RedisObject class which
47
- # also contain the options. This is different from the instance
48
- # RedisObjects defined above which returns the OpenStruct of name, klass, and opts.
49
- #names.collect! { |name| self.send name }
50
- # OR NOT:
51
- names.collect! { |name| class_redis_objects[name] }
52
- names
53
- end
54
- end
55
- def inherited(obj)
56
- obj.db = self.db
57
- obj.uri = self.uri
58
- obj.ttl = self.ttl
59
- obj.parent = self
60
- obj.class_zset :instances, :class => obj, :reference => true
61
- Familia.classes << obj
62
- super(obj)
63
- end
64
- def extended(obj)
65
- obj.db = self.db
66
- obj.ttl = self.ttl
67
- obj.uri = self.uri
68
- obj.parent = self
69
- obj.class_zset :instances, :class => obj, :reference => true
70
- Familia.classes << obj
71
- end
72
-
73
- # Creates an instance method called +name+ that
74
- # returns an instance of the RedisObject +klass+
75
- def install_redis_object name, klass, opts
76
- raise ArgumentError, "Name is blank" if name.to_s.empty?
77
- name = name.to_s.to_sym
78
- opts ||= {}
79
- redis_objects_order << name
80
- redis_objects[name] = OpenStruct.new
81
- redis_objects[name].name = name
82
- redis_objects[name].klass = klass
83
- redis_objects[name].opts = opts
84
- self.send :attr_reader, name
85
- define_method "#{name}=" do |v|
86
- self.send(name).replace v
87
- end
88
- define_method "#{name}?" do
89
- !self.send(name).empty?
90
- end
91
- redis_objects[name]
92
- end
93
-
94
- def qstamp quantum=nil, pattern=nil, now=Familia.now
95
- quantum ||= ttl || 10.minutes
96
- pattern ||= '%H%M'
97
- rounded = now - (now % quantum)
98
- Time.at(rounded).utc.strftime(pattern)
99
- end
100
-
101
- # Creates a class method called +name+ that
102
- # returns an instance of the RedisObject +klass+
103
- def install_class_redis_object name, klass, opts
104
- raise ArgumentError, "Name is blank" if name.to_s.empty?
105
- name = name.to_s.to_sym
106
- opts = opts.nil? ? {} : opts.clone
107
- opts[:parent] = self unless opts.has_key?(:parent)
108
- # TODO: investigate using attic.redis_objects
109
- class_redis_objects_order << name
110
- class_redis_objects[name] = OpenStruct.new
111
- class_redis_objects[name].name = name
112
- class_redis_objects[name].klass = klass
113
- class_redis_objects[name].opts = opts
114
- # An accessor method created in the metclass will
115
- # access the instance variables for this class.
116
- attic.send :attr_reader, name
117
- attic.send :define_method, "#{name}=" do |v|
118
- send(name).replace v
119
- end
120
- attic.send :define_method, "#{name}?" do
121
- !send(name).empty?
122
- end
123
- redis_object = klass.new name, opts
124
- redis_object.freeze
125
- self.instance_variable_set("@#{name}", redis_object)
126
- class_redis_objects[name]
127
- end
128
-
129
- def from_redisdump dump
130
- dump # todo
131
- end
132
- attr_accessor :parent
133
- def ttl v=nil
134
- @ttl = v unless v.nil?
135
- @ttl || (parent ? parent.ttl : nil)
136
- end
137
- def ttl=(v) @ttl = v end
138
- def db v=nil
139
- @db = v unless v.nil?
140
- @db || (parent ? parent.db : nil)
141
- end
142
- def db=(db) @db = db end
143
- def host(host=nil) @host = host if host; @host end
144
- def host=(host) @host = host end
145
- def port(port=nil) @port = port if port; @port end
146
- def port=(port) @port = port end
147
- def uri=(uri)
148
- uri = URI.parse uri if String === uri
149
- @uri = uri
150
- end
151
- def uri(uri=nil)
152
- self.uri = uri if !uri.to_s.empty?
153
- @uri ||= (parent ? parent.uri : Familia.uri)
154
- @uri.db = @db if @db && @uri.db.to_s != @db.to_s
155
- @uri
156
- end
157
- def redis
158
- Familia.redis uri
159
- end
160
- def flushdb
161
- Familia.info "flushing #{uri}"
162
- redis.flushdb
163
- end
164
- def keys(suffix=nil)
165
- self.redis.keys(rediskey('*',suffix)) || []
166
- end
167
- def all(suffix=:object)
168
- # objects that could not be parsed will be nil
169
- keys(suffix).collect { |k| from_key(k) }.compact
170
- end
171
- def any?(filter='*')
172
- size(filter) > 0
173
- end
174
- def size(filter='*')
175
- self.redis.keys(rediskey(filter)).compact.size
176
- end
177
- def suffix(a=nil, &blk)
178
- @suffix = a || blk if a || !blk.nil?
179
- val = @suffix || Familia.default_suffix
180
- val
181
- end
182
- def prefix=(a) @prefix = a end
183
- def prefix(a=nil) @prefix = a if a; @prefix || self.name.downcase.gsub('::', Familia.delim).to_sym end
184
- # TODO: grab db, ttl, uri from parent
185
- #def parent=(a) @parent = a end
186
- #def parent(a=nil) @parent = a if a; @parent end
187
- def index(i=nil, &blk)
188
- @index = i || blk if i || !blk.nil?
189
- @index ||= Familia.index
190
- @index
191
- end
192
- def suffixes
193
- redis_objects.keys.uniq
194
- end
195
- def class_redis_objects_order
196
- @class_redis_objects_order ||= []
197
- @class_redis_objects_order
198
- end
199
- def class_redis_objects
200
- @class_redis_objects ||= {}
201
- @class_redis_objects
202
- end
203
- def class_redis_objects? name
204
- class_redis_objects.has_key? name.to_s.to_sym
205
- end
206
- def redis_object? name
207
- redis_objects.has_key? name.to_s.to_sym
208
- end
209
- def redis_objects_order
210
- @redis_objects_order ||= []
211
- @redis_objects_order
212
- end
213
- def redis_objects
214
- @redis_objects ||= {}
215
- @redis_objects
216
- end
217
- def create *args
218
- me = from_array *args
219
- raise "#{self} exists: #{me.rediskey}" if me.exists?
220
- me.save
221
- me
222
- end
223
- def multiget(*ids)
224
- ids = rawmultiget(*ids)
225
- ids.compact.collect { |json| self.from_json(json) }.compact
226
- end
227
- def rawmultiget(*ids)
228
- ids.collect! { |objid| rediskey(objid) }
229
- return [] if ids.compact.empty?
230
- Familia.trace :MULTIGET, self.redis, "#{ids.size}: #{ids}", caller if Familia.debug?
231
- ids = self.redis.mget *ids
232
- end
233
-
234
- # Returns an instance based on +idx+ otherwise it
235
- # creates and saves a new instance base on +idx+.
236
- # See from_index
237
- def load_or_create idx
238
- return from_redis(idx) if exists?(idx)
239
- obj = from_index idx
240
- obj.save
241
- obj
242
- end
243
- # Note +idx+ needs to be an appropriate index for
244
- # the given class. If the index is multi-value it
245
- # must be passed as an Array in the proper order.
246
- # Does not call save.
247
- def from_index idx
248
- obj = new
249
- obj.index = idx
250
- obj
251
- end
252
- def from_key objkey
253
- raise ArgumentError, "Empty key" if objkey.to_s.empty?
254
- Familia.trace :LOAD, Familia.redis(self.uri), objkey, caller if Familia.debug?
255
- obj = Familia::String.new objkey, :class => self
256
- obj.value
257
- end
258
- def from_redis idx, suffix=:object
259
- return nil if idx.to_s.empty?
260
- objkey = rediskey idx, suffix
261
- #Familia.trace :FROMREDIS, Familia.redis(self.uri), objkey, caller.first if Familia.debug?
262
- me = from_key objkey
263
- me
264
- end
265
- def exists? idx, suffix=:object
266
- return false if idx.to_s.empty?
267
- objkey = rediskey idx, suffix
268
- ret = Familia.redis(self.uri).exists objkey
269
- Familia.trace :EXISTS, Familia.redis(self.uri), "#{rediskey(idx, suffix)} #{ret}", caller if Familia.debug?
270
- ret
271
- end
272
- def destroy! idx, suffix=:object
273
- ret = Familia.redis(self.uri).del rediskey(idx, suffix)
274
- Familia.trace :DELETED, Familia.redis(self.uri), "#{rediskey(idx, suffix)}: #{ret}", caller if Familia.debug?
275
- ret
276
- end
277
- def find suffix='*'
278
- list = Familia.redis(self.uri).keys(rediskey('*', suffix)) || []
279
- end
280
- # idx can be a value or an Array of values used to create the index.
281
- # We don't enforce a default suffix; that's left up to the instance.
282
- # A nil +suffix+ will not be included in the key.
283
- def rediskey idx, suffix=self.suffix
284
- raise RuntimeError, "No index for #{self}" if idx.to_s.empty?
285
- idx = Familia.join *idx if Array === idx
286
- idx &&= idx.to_s
287
- Familia.rediskey(prefix, idx, suffix)
288
- end
289
- def expand(short_idx, suffix=self.suffix)
290
- expand_key = Familia.rediskey(self.prefix, "#{short_idx}*", suffix)
291
- Familia.trace :EXPAND, Familia.redis(self.uri), expand_key, caller.first if Familia.debug?
292
- list = Familia.redis(self.uri).keys expand_key
293
- case list.size
294
- when 0
295
- nil
296
- when 1
297
- matches = list.first.match(/\A#{Familia.rediskey(prefix)}\:(.+?)\:#{suffix}/) || []
298
- matches[1]
299
- else
300
- raise Familia::NonUniqueKey, "Short key returned more than 1 match"
301
- end
302
- end
303
- end
304
-
305
-
306
- module InstanceMethods
307
-
308
- # A default initialize method. This will be replaced
309
- # if a class defines its own initialize method after
310
- # including Familia. In that case, the replacement
311
- # must call initialize_redis_objects.
312
- def initialize *args
313
- initialize_redis_objects
314
- init *args if respond_to? :init
315
- end
316
-
317
- # This needs to be called in the initialize method of
318
- # any class that includes Familia.
319
- def initialize_redis_objects
320
- # Generate instances of each RedisObject. These need to be
321
- # unique for each instance of this class so they can refer
322
- # to the index of this specific instance.
323
- #
324
- # i.e.
325
- # familia_object.rediskey == v1:bone:INDEXVALUE:object
326
- # familia_object.redis_object.rediskey == v1:bone:INDEXVALUE:name
327
- #
328
- # See RedisObject.install_redis_object
329
- self.class.redis_objects.each_pair do |name, redis_object_definition|
330
- klass, opts = redis_object_definition.klass, redis_object_definition.opts
331
- opts = opts.nil? ? {} : opts.clone
332
- opts[:parent] = self unless opts.has_key?(:parent)
333
- redis_object = klass.new name, opts
334
- redis_object.freeze
335
- self.instance_variable_set "@#{name}", redis_object
336
- end
337
- end
338
-
339
- def qstamp quantum=nil, pattern=nil, now=Familia.now
340
- self.class.qstamp ttl, pattern, now
341
- end
342
-
343
- def from_redis
344
- self.class.from_redis self.index
345
- end
346
-
347
- def redis
348
- self.class.redis
349
- end
350
-
351
- def redisinfo
352
- info = {
353
- :uri => self.class.uri,
354
- :db => self.class.db,
355
- :key => rediskey,
356
- :type => redistype,
357
- :ttl => realttl
358
- }
359
- end
360
- def exists?
361
- Familia.redis(self.class.uri).exists rediskey
362
- end
363
-
364
- #def rediskeys
365
- # self.class.redis_objects.each do |redis_object_definition|
366
- #
367
- # end
368
- #end
369
-
370
- def allkeys
371
- # TODO: Use redis_objects instead
372
- keynames = []
373
- self.class.suffixes.each do |sfx|
374
- keynames << rediskey(sfx)
375
- end
376
- keynames
377
- end
378
- # +suffix+ is the value to be used at the end of the redis key
379
- # + ignored+ is literally ignored. It's around to maintain
380
- # consistency with the class version of this method.
381
- # (RedisObject#rediskey may call against a class or instance).
382
- def rediskey(suffix=nil, ignored=nil)
383
- Familia.info "[#{self.class}] something was ignored" unless ignored.nil?
384
- raise Familia::NoIndex, self.class if index.to_s.empty?
385
- if suffix.nil?
386
- suffix = self.class.suffix.kind_of?(Proc) ?
387
- self.class.suffix.call(self) :
388
- self.class.suffix
389
- end
390
- self.class.rediskey self.index, suffix
391
- end
392
- def object_proxy
393
- @object_proxy ||= Familia::String.new self.rediskey, :ttl => ttl, :class => self.class
394
- @object_proxy
395
- end
396
- def save meth=:set
397
- #Familia.trace :SAVE, Familia.redis(self.class.uri), redisuri, caller.first if Familia.debug?
398
- preprocess if respond_to?(:preprocess)
399
- self.update_time if self.respond_to?(:update_time)
400
- ret = object_proxy.send(meth, self) # object is a name reserved by Familia
401
- unless ret.nil?
402
- now = Time.now.utc.to_i
403
- self.class.instances.add now, self # use this set instead of Klass.keys
404
- object_proxy.update_expiration # does nothing unless if not specified
405
- end
406
- ret == "OK" || ret == true || ret == 1
407
- end
408
- def savenx
409
- save :setnx
410
- end
411
- def update! hsh=nil
412
- updated = false
413
- hsh ||= {}
414
- if hsh.empty?
415
- raise Familia::Problem, "No #{self.class}#{to_hash} method" unless respond_to?(:to_hash)
416
- ret = from_redis
417
- hsh = ret.to_hash if ret
418
- end
419
- hsh.keys.each { |field|
420
- v = hsh[field.to_s] || hsh[field.to_s.to_sym]
421
- next if v.nil?
422
- self.send(:"#{field}=", v)
423
- updated = true
424
- }
425
- updated
426
- end
427
- def destroy!
428
- ret = object_proxy.delete
429
- if Familia.debug?
430
- Familia.trace :DELETED, Familia.redis(self.class.uri), "#{rediskey}: #{ret}", caller.first if Familia.debug?
431
- end
432
- self.class.instances.rem self if ret > 0
433
- ret
434
- end
435
- def index
436
- case self.class.index
437
- when Proc
438
- self.class.index.call(self)
439
- when Array
440
- parts = self.class.index.collect { |meth|
441
- unless self.respond_to? meth
442
- raise NoIndex, "No such method: `#{meth}' for #{self.class}"
443
- end
444
- ret = self.send(meth)
445
- ret = ret.index if ret.kind_of?(Familia)
446
- ret
447
- }
448
- parts.join Familia.delim
449
- when Symbol, String
450
- if self.class.redis_object?(self.class.index.to_sym)
451
- raise Familia::NoIndex, "Cannot use a RedisObject as an index"
452
- else
453
- unless self.respond_to? self.class.index
454
- raise NoIndex, "No such method: `#{self.class.index}' for #{self.class}"
455
- end
456
- ret = self.send(self.class.index)
457
- ret = ret.index if ret.kind_of?(Familia)
458
- ret
459
- end
460
- else
461
- raise Familia::NoIndex, self
462
- end
463
- end
464
- def index=(v)
465
- case self.class.index
466
- when Proc
467
- raise ArgumentError, "Cannot set a Proc index"
468
- when Array
469
- unless Array === v && v.size == self.class.index.size
470
- raise ArgumentError, "Index mismatch (#{v.size} for #{self.class.index.size})"
471
- end
472
- parts = self.class.index.each_with_index { |meth,idx|
473
- unless self.respond_to? "#{meth}="
474
- raise NoIndex, "No such method: `#{meth}=' for #{self.class}"
475
- end
476
- self.send("#{meth}=", v[idx])
477
- }
478
- when Symbol, String
479
- if self.class.redis_object?(self.class.index.to_sym)
480
- raise Familia::NoIndex, "Cannot use a RedisObject as an index"
481
- else
482
- unless self.respond_to? "#{self.class.index}="
483
- raise NoIndex, "No such method: `#{self.class.index}=' for #{self.class}"
484
- end
485
- self.send("#{self.class.index}=", v)
486
- end
487
- else
488
- raise Familia::NoIndex, self
489
- end
490
-
491
- end
492
- def expire(ttl=nil)
493
- ttl ||= self.class.ttl
494
- Familia.redis(self.class.uri).expire rediskey, ttl.to_i
495
- end
496
- def realttl
497
- Familia.redis(self.class.uri).ttl rediskey
498
- end
499
- def ttl=(v)
500
- @ttl = v.to_i
501
- end
502
- def ttl
503
- @ttl || self.class.ttl
504
- end
505
- def raw(suffix=nil)
506
- suffix ||= :object
507
- Familia.redis(self.class.uri).get rediskey(suffix)
508
- end
509
- def redisuri(suffix=nil)
510
- u = URI.parse self.class.uri.to_s
511
- u.db ||= self.class.db.to_s
512
- u.key = rediskey(suffix)
513
- u
514
- end
515
- def redistype(suffix=nil)
516
- Familia.redis(self.class.uri).type rediskey(suffix)
517
- end
518
- # Finds the shortest available unique key (lower limit of 6)
519
- def shortid
520
- len = 6
521
- loop do
522
- begin
523
- self.class.expand(@id.shorten(len))
524
- break
525
- rescue Familia::NonUniqueKey
526
- len += 1
527
- end
528
- end
529
- @id.shorten(len)
530
- end
531
- end
532
-
533
- end