familia 0.7.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e5c7b9afb9555b1223964ed3e93ac014df3c278e9c7467dfd26c5fced66e7298
4
+ data.tar.gz: 5ec4da06d0223bc8f27a2ba0689df6482dfbaf971028c3431bdc330cad332c13
5
+ SHA512:
6
+ metadata.gz: 7341bb23aa95bba387ab28377563d63d80933e99f3e5034ba65643c4806d7e7d7fc98308e9fc7ec7358d7c9775129bf0f02b198abd57b2b99bb1b28df0530a46
7
+ data.tar.gz: d4bbd6719f096d7f58ea00b0ae7a7ba2f5da71365b21b818863fc122b8a3856607248a803367ca9c87adc25b8d85b935331ce1413da4f803254baacf68cbb464
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ .DS_Store
2
+ .bundle
3
+ .byebug*
4
+ .history
5
+ .devcontainer
6
+ .vscode
7
+ *.env
8
+ *.log
9
+ *.md
10
+ *.txt
11
+ !LICENSE.txt
12
+ .ruby-version
13
+ appendonlydir
14
+ etc/config
15
+ log
16
+ tmp
17
+ vendor
18
+ *.gem
data/CHANGES.txt CHANGED
@@ -1,5 +1,24 @@
1
1
  FAMILIA, CHANGES
2
2
 
3
+
4
+ #### 0.7.1 (2011-04-09) ###############################
5
+
6
+ * FIXED: Explicitly convert boolean conf options to true or false. Redis client
7
+ treats "true" as false.
8
+ * FIXED: Clone options for each RedisObject instance to prevent cross contamination.
9
+ * CHANGE: Fix for Familia objects with custom suffixes
10
+ * CHANGE: Familia::String methods that modify the key now automatically update_expiration.
11
+ * CHANGE: Familia::Object.prefix replaces :: in class names with Familia.delim
12
+ * CHANGE: Use Familia::Object#object_proxy instead of Familia::Object.string :object
13
+ * ADDED: Familia.qnow, Familia::Object.qstamp, RedisObject#qstamp
14
+ * ADDED: RedisObject option: quantize
15
+ * ADDED: Familia::Object#savenx
16
+ * ADDED: Can now include a Familia object in Familia::Object.index (it uses its index)
17
+ * ADDED: Dependency multi_json
18
+ * ADDED: Hash#to_json, Hash.from_json, Array#to_json, Array.from_json
19
+ * ADDED: Familia::Object#update!
20
+
21
+
3
22
  #### 0.7.0 (2011-03-04) ###############################
4
23
 
5
24
  * CHANGE: Use RedisObject#multi_from_redis when possible (uses mget command)
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby '>= 2.6.8'
4
+
5
+ gem 'redis', '~> 4.8'
6
+ gem 'uri-redis'
7
+ gem 'gibbler'
8
+ gem 'storable'
9
+ gem 'multi_json'
10
+
11
+ group :development, :test do
12
+ gem 'tryouts', '~> 2.2'
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ attic (1.0.0)
5
+ rake (~> 13.0.6)
6
+ drydock (0.6.9)
7
+ gibbler (1.0.0)
8
+ attic (~> 1.0)
9
+ rake (~> 13.0)
10
+ multi_json (1.15.0)
11
+ rake (13.0.6)
12
+ redis (4.8.1)
13
+ storable (0.10.0)
14
+ sysinfo (0.10.0)
15
+ drydock (< 1.0)
16
+ storable (~> 0.10)
17
+ tryouts (2.2.0)
18
+ sysinfo (~> 0.10)
19
+ uri-redis (0.4.2)
20
+
21
+ PLATFORMS
22
+ arm64-darwin-22
23
+
24
+ DEPENDENCIES
25
+ gibbler
26
+ multi_json
27
+ redis (~> 4.8)
28
+ storable
29
+ tryouts (~> 2.2)
30
+ uri-redis
31
+
32
+ RUBY VERSION
33
+ ruby 2.6.8p205
34
+
35
+ BUNDLED WITH
36
+ 2.4.12
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Familia - 0.9 (2024-04-04)
2
+
3
+ **Organize and store ruby objects in Redis**
4
+
5
+
6
+ ## Basic Example
7
+
8
+ ```ruby
9
+ class Flower < Storable
10
+ include Familia
11
+ index [:token, :name]
12
+ field :token
13
+ field :name
14
+ list :owners
15
+ set :tags
16
+ zset :metrics
17
+ hash :props
18
+ string :value, :default => "GREAT!"
19
+ end
20
+ ```
21
+
22
+ ## More Information
23
+
24
+ * [Codes](https://github.com/delano/familia)
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
- ---
1
+ ---
2
2
  :MAJOR: 0
3
- :MINOR: 7
3
+ :MINOR: 9
4
4
  :PATCH: 0
data/familia.gemspec CHANGED
@@ -1,71 +1,23 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
- # -*- encoding: utf-8 -*-
5
-
6
1
  Gem::Specification.new do |s|
7
- s.name = %q{familia}
8
- s.version = "0.7.0"
2
+ s.name = "familia"
3
+ s.version = "0.9.0"
4
+ s.summary = "Organize and store ruby objects in Redis"
5
+ s.description = "Familia: #{s.summary}"
6
+ s.authors = ["Delano Mandelbaum"]
7
+ s.email = "delano@solutious.com"
8
+ s.homepage = "https://github.com/delano/familia"
9
+ s.license = "MIT"
9
10
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Delano Mandelbaum"]
12
- s.date = %q{2011-03-04}
13
- s.description = %q{Organize and store ruby objects in Redis}
14
- s.email = %q{delano@solutious.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE.txt",
17
- "README.rdoc"
18
- ]
19
- s.files = [
20
- "CHANGES.txt",
21
- "LICENSE.txt",
22
- "README.rdoc",
23
- "Rakefile",
24
- "VERSION.yml",
25
- "familia.gemspec",
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"
42
- ]
43
- s.homepage = %q{http://github.com/delano/familia}
44
- s.rdoc_options = ["--charset=UTF-8"]
11
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
12
+ s.bindir = "exe"
13
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
45
14
  s.require_paths = ["lib"]
46
- s.rubyforge_project = %q{familia}
47
- s.rubygems_version = %q{1.5.2}
48
- s.summary = %q{Organize and store ruby objects in Redis}
49
15
 
50
- if s.respond_to? :specification_version then
51
- s.specification_version = 3
16
+ s.required_ruby_version = Gem::Requirement.new(">= 2.6.8")
52
17
 
53
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
- s.add_runtime_dependency(%q<redis>, [">= 2.1.0"])
55
- s.add_runtime_dependency(%q<uri-redis>, [">= 0.4.2"])
56
- s.add_runtime_dependency(%q<gibbler>, [">= 0.8.6"])
57
- s.add_runtime_dependency(%q<storable>, [">= 0.8.6"])
58
- else
59
- s.add_dependency(%q<redis>, [">= 2.1.0"])
60
- s.add_dependency(%q<uri-redis>, [">= 0.4.2"])
61
- s.add_dependency(%q<gibbler>, [">= 0.8.6"])
62
- s.add_dependency(%q<storable>, [">= 0.8.6"])
63
- end
64
- else
65
- s.add_dependency(%q<redis>, [">= 2.1.0"])
66
- s.add_dependency(%q<uri-redis>, [">= 0.4.2"])
67
- s.add_dependency(%q<gibbler>, [">= 0.8.6"])
68
- s.add_dependency(%q<storable>, [">= 0.8.6"])
69
- end
18
+ s.add_dependency "redis", "~> 4.8.0"
19
+ s.add_dependency "uri-redis", "~> 0.4", ">= 0.4.2"
20
+ s.add_dependency "gibbler", "~> 1.0.0"
21
+ s.add_dependency "storable", "~> 0.10.0"
22
+ s.add_dependency "multi_json", "~> 0.0", ">= 0.0.5"
70
23
  end
71
-
@@ -7,6 +7,27 @@ class Symbol
7
7
  end
8
8
  end
9
9
 
10
+ class Hash
11
+ unless method_defined?(:to_json)
12
+ def to_json
13
+ MultiJson.encode self
14
+ end
15
+ def self.from_json str
16
+ MultiJson.decode str
17
+ end
18
+ end
19
+ end
20
+ class Array
21
+ unless method_defined?(:to_json)
22
+ def to_json
23
+ MultiJson.encode self
24
+ end
25
+ def self.from_json str
26
+ MultiJson.decode str
27
+ end
28
+ end
29
+ end
30
+
10
31
  # Assumes Time::Units and Numeric mixins are available.
11
32
  class String
12
33
  def in_seconds
@@ -2,12 +2,12 @@ require 'ostruct'
2
2
 
3
3
  module Familia
4
4
  require 'familia/redisobject'
5
-
5
+
6
6
  # Auto-extended into a class that includes Familia
7
7
  module ClassMethods
8
-
8
+
9
9
  Familia::RedisObject.registration.each_pair do |kind, klass|
10
- # e.g.
10
+ # e.g.
11
11
  #
12
12
  # list(name, klass, opts)
13
13
  # list?(name)
@@ -22,12 +22,12 @@ module Familia
22
22
  obj = redis_objects[name.to_s.to_sym]
23
23
  !obj.nil? && klass == obj.klass
24
24
  end
25
- define_method :"#{kind}s" do
25
+ define_method :"#{kind}s" do
26
26
  names = redis_objects_order.select { |name| send(:"#{kind}?", name) }
27
27
  names.collect! { |name| redis_objects[name] }
28
28
  names
29
29
  end
30
- # e.g.
30
+ # e.g.
31
31
  #
32
32
  # class_list(name, klass, opts)
33
33
  # class_list?(name)
@@ -41,29 +41,23 @@ module Familia
41
41
  obj = class_redis_objects[name.to_s.to_sym]
42
42
  !obj.nil? && klass == obj.klass
43
43
  end
44
- define_method :"class_#{kind}s" do
44
+ define_method :"class_#{kind}s" do
45
45
  names = class_redis_objects_order.select { |name| ret = send(:"class_#{kind}?", name) }
46
46
  # TODO: This returns instances of the RedisObject class which
47
47
  # also contain the options. This is different from the instance
48
- # RedisObjects defined above which returns the OpenStruct of name, klass, and opts.
48
+ # RedisObjects defined above which returns the OpenStruct of name, klass, and opts.
49
49
  #names.collect! { |name| self.send name }
50
50
  # OR NOT:
51
51
  names.collect! { |name| class_redis_objects[name] }
52
52
  names
53
53
  end
54
54
  end
55
-
56
55
  def inherited(obj)
57
56
  obj.db = self.db
58
57
  obj.uri = self.uri
59
58
  obj.ttl = self.ttl
60
59
  obj.parent = self
61
60
  obj.class_zset :instances, :class => obj, :reference => true
62
- # :object is a special redis object because its reserved
63
- # for storing the marshaled instance data (e.g. to_json).
64
- # When it isn't defined explicitly we define it here b/c
65
- # it's assumed to exist in other places (see #save).
66
- obj.string :object, :class => obj unless obj.redis_object? :object
67
61
  Familia.classes << obj
68
62
  super(obj)
69
63
  end
@@ -75,9 +69,9 @@ module Familia
75
69
  obj.class_zset :instances, :class => obj, :reference => true
76
70
  Familia.classes << obj
77
71
  end
78
-
72
+
79
73
  # Creates an instance method called +name+ that
80
- # returns an instance of the RedisObject +klass+
74
+ # returns an instance of the RedisObject +klass+
81
75
  def install_redis_object name, klass, opts
82
76
  raise ArgumentError, "Name is blank" if name.to_s.empty?
83
77
  name = name.to_s.to_sym
@@ -96,27 +90,34 @@ module Familia
96
90
  end
97
91
  redis_objects[name]
98
92
  end
99
-
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
+
100
101
  # Creates a class method called +name+ that
101
- # returns an instance of the RedisObject +klass+
102
+ # returns an instance of the RedisObject +klass+
102
103
  def install_class_redis_object name, klass, opts
103
104
  raise ArgumentError, "Name is blank" if name.to_s.empty?
104
105
  name = name.to_s.to_sym
105
- opts ||= {}
106
- opts[:parent] ||= self
107
- # TODO: investigate using metaclass.redis_objects
106
+ opts = opts.nil? ? {} : opts.clone
107
+ opts[:parent] = self unless opts.has_key?(:parent)
108
+ # TODO: investigate using attic.redis_objects
108
109
  class_redis_objects_order << name
109
110
  class_redis_objects[name] = OpenStruct.new
110
111
  class_redis_objects[name].name = name
111
112
  class_redis_objects[name].klass = klass
112
- class_redis_objects[name].opts = opts
113
+ class_redis_objects[name].opts = opts
113
114
  # An accessor method created in the metclass will
114
- # access the instance variables for this class.
115
- metaclass.send :attr_reader, name
116
- metaclass.send :define_method, "#{name}=" do |v|
115
+ # access the instance variables for this class.
116
+ attic.send :attr_reader, name
117
+ attic.send :define_method, "#{name}=" do |v|
117
118
  send(name).replace v
118
119
  end
119
- metaclass.send :define_method, "#{name}?" do
120
+ attic.send :define_method, "#{name}?" do
120
121
  !send(name).empty?
121
122
  end
122
123
  redis_object = klass.new name, opts
@@ -124,7 +125,7 @@ module Familia
124
125
  self.instance_variable_set("@#{name}", redis_object)
125
126
  class_redis_objects[name]
126
127
  end
127
-
128
+
128
129
  def from_redisdump dump
129
130
  dump # todo
130
131
  end
@@ -145,9 +146,9 @@ module Familia
145
146
  def port=(port) @port = port end
146
147
  def uri=(uri)
147
148
  uri = URI.parse uri if String === uri
148
- @uri = uri
149
+ @uri = uri
149
150
  end
150
- def uri(uri=nil)
151
+ def uri(uri=nil)
151
152
  self.uri = uri if !uri.to_s.empty?
152
153
  @uri ||= (parent ? parent.uri : Familia.uri)
153
154
  @uri.db = @db if @db && @uri.db.to_s != @db.to_s
@@ -165,7 +166,7 @@ module Familia
165
166
  end
166
167
  def all(suffix=:object)
167
168
  # objects that could not be parsed will be nil
168
- keys(suffix).collect { |k| from_key(k) }.compact
169
+ keys(suffix).collect { |k| from_key(k) }.compact
169
170
  end
170
171
  def any?(filter='*')
171
172
  size(filter) > 0
@@ -173,17 +174,17 @@ module Familia
173
174
  def size(filter='*')
174
175
  self.redis.keys(rediskey(filter)).compact.size
175
176
  end
176
- def suffix(a=nil, &blk)
177
+ def suffix(a=nil, &blk)
177
178
  @suffix = a || blk if a || !blk.nil?
178
179
  val = @suffix || Familia.default_suffix
179
180
  val
180
181
  end
181
182
  def prefix=(a) @prefix = a end
182
- def prefix(a=nil) @prefix = a if a; @prefix || self.name.downcase.to_sym end
183
+ def prefix(a=nil) @prefix = a if a; @prefix || self.name.downcase.gsub('::', Familia.delim).to_sym end
183
184
  # TODO: grab db, ttl, uri from parent
184
185
  #def parent=(a) @parent = a end
185
186
  #def parent(a=nil) @parent = a if a; @parent end
186
- def index(i=nil, &blk)
187
+ def index(i=nil, &blk)
187
188
  @index = i || blk if i || !blk.nil?
188
189
  @index ||= Familia.index
189
190
  @index
@@ -229,9 +230,9 @@ module Familia
229
230
  Familia.trace :MULTIGET, self.redis, "#{ids.size}: #{ids}", caller if Familia.debug?
230
231
  ids = self.redis.mget *ids
231
232
  end
232
-
233
+
233
234
  # Returns an instance based on +idx+ otherwise it
234
- # creates and saves a new instance base on +idx+.
235
+ # creates and saves a new instance base on +idx+.
235
236
  # See from_index
236
237
  def load_or_create idx
237
238
  return from_redis(idx) if exists?(idx)
@@ -239,17 +240,17 @@ module Familia
239
240
  obj.save
240
241
  obj
241
242
  end
242
- # Note +idx+ needs to be an appropriate index for
243
+ # Note +idx+ needs to be an appropriate index for
243
244
  # the given class. If the index is multi-value it
244
245
  # must be passed as an Array in the proper order.
245
246
  # Does not call save.
246
247
  def from_index idx
247
- obj = new
248
+ obj = new
248
249
  obj.index = idx
249
250
  obj
250
251
  end
251
252
  def from_key objkey
252
- raise ArgumentError, "Empty key" if objkey.to_s.empty?
253
+ raise ArgumentError, "Empty key" if objkey.to_s.empty?
253
254
  Familia.trace :LOAD, Familia.redis(self.uri), objkey, caller if Familia.debug?
254
255
  obj = Familia::String.new objkey, :class => self
255
256
  obj.value
@@ -292,18 +293,18 @@ module Familia
292
293
  case list.size
293
294
  when 0
294
295
  nil
295
- when 1
296
+ when 1
296
297
  matches = list.first.match(/\A#{Familia.rediskey(prefix)}\:(.+?)\:#{suffix}/) || []
297
298
  matches[1]
298
299
  else
299
- raise Familia::NonUniqueKey, "Short key returned more than 1 match"
300
+ raise Familia::NonUniqueKey, "Short key returned more than 1 match"
300
301
  end
301
302
  end
302
303
  end
303
304
 
304
-
305
+
305
306
  module InstanceMethods
306
-
307
+
307
308
  # A default initialize method. This will be replaced
308
309
  # if a class defines its own initialize method after
309
310
  # including Familia. In that case, the replacement
@@ -312,33 +313,41 @@ module Familia
312
313
  initialize_redis_objects
313
314
  init *args if respond_to? :init
314
315
  end
315
-
316
+
316
317
  # This needs to be called in the initialize method of
317
- # any class that includes Familia.
318
+ # any class that includes Familia.
318
319
  def initialize_redis_objects
319
320
  # Generate instances of each RedisObject. These need to be
320
321
  # unique for each instance of this class so they can refer
321
322
  # to the index of this specific instance.
322
323
  #
323
- # i.e.
324
+ # i.e.
324
325
  # familia_object.rediskey == v1:bone:INDEXVALUE:object
325
326
  # familia_object.redis_object.rediskey == v1:bone:INDEXVALUE:name
326
327
  #
327
328
  # See RedisObject.install_redis_object
328
329
  self.class.redis_objects.each_pair do |name, redis_object_definition|
329
330
  klass, opts = redis_object_definition.klass, redis_object_definition.opts
330
- opts ||= {}
331
- opts[:parent] ||= self
331
+ opts = opts.nil? ? {} : opts.clone
332
+ opts[:parent] = self unless opts.has_key?(:parent)
332
333
  redis_object = klass.new name, opts
333
334
  redis_object.freeze
334
335
  self.instance_variable_set "@#{name}", redis_object
335
336
  end
336
337
  end
337
-
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
+
338
347
  def redis
339
348
  self.class.redis
340
349
  end
341
-
350
+
342
351
  def redisinfo
343
352
  info = {
344
353
  :uri => self.class.uri,
@@ -350,51 +359,73 @@ module Familia
350
359
  end
351
360
  def exists?
352
361
  Familia.redis(self.class.uri).exists rediskey
353
- end
354
-
362
+ end
363
+
355
364
  #def rediskeys
356
365
  # self.class.redis_objects.each do |redis_object_definition|
357
- #
366
+ #
358
367
  # end
359
368
  #end
360
-
369
+
361
370
  def allkeys
362
371
  # TODO: Use redis_objects instead
363
372
  keynames = []
364
- self.class.suffixes.each do |sfx|
373
+ self.class.suffixes.each do |sfx|
365
374
  keynames << rediskey(sfx)
366
375
  end
367
376
  keynames
368
377
  end
369
378
  # +suffix+ is the value to be used at the end of the redis key
370
379
  # + ignored+ is literally ignored. It's around to maintain
371
- # consistency with the class version of this method.
380
+ # consistency with the class version of this method.
372
381
  # (RedisObject#rediskey may call against a class or instance).
373
382
  def rediskey(suffix=nil, ignored=nil)
374
383
  Familia.info "[#{self.class}] something was ignored" unless ignored.nil?
375
384
  raise Familia::NoIndex, self.class if index.to_s.empty?
376
385
  if suffix.nil?
377
- suffix = self.class.suffix.kind_of?(Proc) ?
378
- self.class.suffix.call(self) :
386
+ suffix = self.class.suffix.kind_of?(Proc) ?
387
+ self.class.suffix.call(self) :
379
388
  self.class.suffix
380
389
  end
381
390
  self.class.rediskey self.index, suffix
382
391
  end
383
- def save
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
384
397
  #Familia.trace :SAVE, Familia.redis(self.class.uri), redisuri, caller.first if Familia.debug?
385
398
  preprocess if respond_to?(:preprocess)
386
399
  self.update_time if self.respond_to?(:update_time)
387
- # TODO: Check here (run checkup)
388
- ret = self.object.set self # object is a name reserved by Familia
400
+ ret = object_proxy.send(meth, self) # object is a name reserved by Familia
389
401
  unless ret.nil?
390
402
  now = Time.now.utc.to_i
391
403
  self.class.instances.add now, self # use this set instead of Klass.keys
392
- self.object.update_expiration self.ttl # does nothing unless if not specified
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
393
418
  end
394
- true
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
395
426
  end
396
427
  def destroy!
397
- ret = self.object.delete
428
+ ret = object_proxy.delete
398
429
  if Familia.debug?
399
430
  Familia.trace :DELETED, Familia.redis(self.class.uri), "#{rediskey}: #{ret}", caller.first if Familia.debug?
400
431
  end
@@ -406,11 +437,13 @@ module Familia
406
437
  when Proc
407
438
  self.class.index.call(self)
408
439
  when Array
409
- parts = self.class.index.collect { |meth|
440
+ parts = self.class.index.collect { |meth|
410
441
  unless self.respond_to? meth
411
442
  raise NoIndex, "No such method: `#{meth}' for #{self.class}"
412
443
  end
413
- self.send(meth)
444
+ ret = self.send(meth)
445
+ ret = ret.index if ret.kind_of?(Familia)
446
+ ret
414
447
  }
415
448
  parts.join Familia.delim
416
449
  when Symbol, String
@@ -420,7 +453,9 @@ module Familia
420
453
  unless self.respond_to? self.class.index
421
454
  raise NoIndex, "No such method: `#{self.class.index}' for #{self.class}"
422
455
  end
423
- self.send( self.class.index)
456
+ ret = self.send(self.class.index)
457
+ ret = ret.index if ret.kind_of?(Familia)
458
+ ret
424
459
  end
425
460
  else
426
461
  raise Familia::NoIndex, self
@@ -434,11 +469,11 @@ module Familia
434
469
  unless Array === v && v.size == self.class.index.size
435
470
  raise ArgumentError, "Index mismatch (#{v.size} for #{self.class.index.size})"
436
471
  end
437
- parts = self.class.index.each_with_index { |meth,idx|
472
+ parts = self.class.index.each_with_index { |meth,idx|
438
473
  unless self.respond_to? "#{meth}="
439
474
  raise NoIndex, "No such method: `#{meth}=' for #{self.class}"
440
475
  end
441
- self.send("#{meth}=", v[idx])
476
+ self.send("#{meth}=", v[idx])
442
477
  }
443
478
  when Symbol, String
444
479
  if self.class.redis_object?(self.class.index.to_sym)
@@ -452,7 +487,7 @@ module Familia
452
487
  else
453
488
  raise Familia::NoIndex, self
454
489
  end
455
-
490
+
456
491
  end
457
492
  def expire(ttl=nil)
458
493
  ttl ||= self.class.ttl
@@ -491,8 +526,8 @@ module Familia
491
526
  len += 1
492
527
  end
493
528
  end
494
- @id.shorten(len)
529
+ @id.shorten(len)
495
530
  end
496
531
  end
497
-
498
- end
532
+
533
+ end