familia 1.0.0.pre.rc7 → 1.1.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +3 -1
  4. data/README.md +5 -5
  5. data/VERSION.yml +2 -2
  6. data/familia.gemspec +1 -2
  7. data/lib/familia/base.rb +11 -11
  8. data/lib/familia/connection.rb +18 -2
  9. data/lib/familia/errors.rb +14 -0
  10. data/lib/familia/features/expiration.rb +13 -2
  11. data/lib/familia/features/quantization.rb +1 -1
  12. data/lib/familia/horreum/class_methods.rb +31 -21
  13. data/lib/familia/horreum/commands.rb +53 -14
  14. data/lib/familia/horreum/relations_management.rb +9 -2
  15. data/lib/familia/horreum/serialization.rb +32 -23
  16. data/lib/familia/horreum.rb +5 -1
  17. data/lib/familia/redistype/commands.rb +5 -2
  18. data/lib/familia/redistype/serialization.rb +17 -16
  19. data/lib/familia/redistype/types/hashkey.rb +166 -0
  20. data/lib/familia/{types → redistype/types}/list.rb +19 -14
  21. data/lib/familia/{types → redistype/types}/sorted_set.rb +23 -19
  22. data/lib/familia/{types → redistype/types}/string.rb +8 -6
  23. data/lib/familia/{types → redistype/types}/unsorted_set.rb +16 -12
  24. data/lib/familia/redistype.rb +19 -9
  25. data/lib/familia.rb +5 -1
  26. data/try/10_familia_try.rb +1 -1
  27. data/try/20_redis_type_try.rb +1 -1
  28. data/try/21_redis_type_zset_try.rb +1 -1
  29. data/try/22_redis_type_set_try.rb +1 -1
  30. data/try/23_redis_type_list_try.rb +2 -2
  31. data/try/24_redis_type_string_try.rb +3 -3
  32. data/try/25_redis_type_hash_try.rb +1 -1
  33. data/try/26_redis_bool_try.rb +2 -2
  34. data/try/27_redis_horreum_try.rb +2 -2
  35. data/try/30_familia_object_try.rb +8 -5
  36. data/try/40_customer_try.rb +6 -6
  37. metadata +15 -15
  38. data/lib/familia/types/hashkey.rb +0 -108
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 518b9d319e506964416166f2d84cc05d9d4a603de80b79a2f512fa9b751c4680
4
- data.tar.gz: 9f1c9737d5d141050169da6123a977921e80768f056a0da138c299ccd7716abc
3
+ metadata.gz: f7e5fe48e629eab30af342be4ad81f6afe5854b33b678b926b9c3470c0515627
4
+ data.tar.gz: 1d938baae35feb17f3d9855055152fdb0ba441317315fd3b2de6932e3c47e0cf
5
5
  SHA512:
6
- metadata.gz: 0fed4bf1187d6d9ad477504343341c1f9a5bd59d0b7c276fbb8539086ddacfd2fa2306a55f9b2b10881cdd884ad38e6a9f392f79ed822fff270d97836d681260
7
- data.tar.gz: 448a16b7b71afa58a27f0dfd464d85c495ddd517bf9eba5c622fab6c7b364566595e34d71934a2b9cd67b522e4b53ad94289a8097c8d819c011ad7e3e61bd6fe
6
+ metadata.gz: 707e9aba376653fa439ab1ffc85c4b794f1f52444dbed86a093f1d9c93a1952d8f84fe465cdcf8798bf0f72e5a4099586f1895cad5225f6d81899deeedcbf3a5
7
+ data.tar.gz: a9323482330bef4a632d92fd6828da32f0be7aa4f107d659e4440a2e3a09ad3dd0c2011d90a5e1a3380ca85fdf5afe47fb4634e9cf21951986d92115523bfbba
data/Gemfile CHANGED
@@ -5,7 +5,9 @@ source 'https://rubygems.org'
5
5
  gemspec
6
6
 
7
7
  group :development, :test do
8
- gem 'pry-byebug', '~> 3.10.1', require: false
8
+ # byebug only works with MRI
9
+ gem 'byebug', '~> 11.0', require: false if RUBY_ENGINE == 'ruby'
10
+ gem 'pry-byebug', '~> 3.10.1', require: false if RUBY_ENGINE == 'ruby'
9
11
  gem 'rubocop', require: false
10
12
  gem 'rubocop-performance', require: false
11
13
  gem 'rubocop-thread_safety', require: false
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (1.0.0.pre.rc7)
4
+ familia (1.1.0.pre.rc1)
5
5
  redis (>= 4.8.1, < 6.0)
6
+ stringio (~> 3.1.1)
6
7
  uri-redis (~> 1.3)
7
8
 
8
9
  GEM
@@ -55,6 +56,7 @@ GEM
55
56
  rubocop (>= 0.90.0)
56
57
  ruby-progressbar (1.13.0)
57
58
  storable (0.10.0)
59
+ stringio (3.1.1)
58
60
  strscan (3.1.0)
59
61
  sysinfo (0.10.0)
60
62
  drydock (< 1.0)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Familia - 1.0.0-rc7 (August 2024)
1
+ # Familia - 1.1.0-rc1 (November 2024)
2
2
 
3
3
  **Organize and store Ruby objects in Redis. A powerful Ruby ORM (of sorts) for Redis.**
4
4
 
@@ -9,7 +9,7 @@ Familia provides a flexible and feature-rich way to interact with Redis using Ru
9
9
 
10
10
  Get it in one of the following ways:
11
11
 
12
- * In your Gemfile: `gem 'familia', '>= 1.0.0-rc4'`
12
+ * In your Gemfile: `gem 'familia', '>= 1.1.0-rc1'`
13
13
  * Install it by hand: `gem install familia --pre`
14
14
  * Or for development: `git clone git@github.com:delano/familia.git`
15
15
 
@@ -152,11 +152,11 @@ end
152
152
 
153
153
  ```ruby
154
154
  class ComplexObject < Familia::Horreum
155
- def to_redis
155
+ def serialize_value
156
156
  custom_serialization_method
157
157
  end
158
158
 
159
- def self.from_redis(data)
159
+ def self.deserialize_value(data)
160
160
  custom_deserialization_method(data)
161
161
  end
162
162
  end
@@ -188,7 +188,7 @@ flower.save
188
188
  ### Retrieving and Updating Objects
189
189
 
190
190
  ```ruby
191
- rose = Flower.from_identifier("rrose")
191
+ rose = Flower.find_by_id("rrose")
192
192
  rose.name = "Pink Rose"
193
193
  rose.save
194
194
  ```
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :MAJOR: 1
3
- :MINOR: 0
3
+ :MINOR: 1
4
4
  :PATCH: 0
5
- :PRE: rc7
5
+ :PRE: rc1
data/familia.gemspec CHANGED
@@ -20,9 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.required_ruby_version = Gem::Requirement.new('>= 2.7.8')
21
21
 
22
22
  spec.add_dependency 'redis', '>= 4.8.1', '< 6.0'
23
+ spec.add_dependency 'stringio', '~> 3.1.1'
23
24
  spec.add_dependency 'uri-redis', '~> 1.3'
24
25
 
25
- # byebug only works with MRI
26
- spec.add_development_dependency 'byebug', '~> 11.0' if RUBY_ENGINE == 'ruby'
27
26
  spec.metadata['rubygems_mfa_required'] = 'true'
28
27
  end
data/lib/familia/base.rb CHANGED
@@ -30,21 +30,21 @@ module Familia
30
30
  end
31
31
  end
32
32
 
33
- # Yo, this class is like that one friend who never checks expiration dates.
34
- # It's living life on the edge, data-style!
33
+ # Base implementation of update_expiration that maintains API compatibility
34
+ # with the :expiration feature's implementation.
35
35
  #
36
- # @param ttl [Integer, nil] Time To Live? More like Time To Laugh! This param
37
- # is here for compatibility, but it's as useful as a chocolate teapot.
36
+ # This is a no-op implementation that gets overridden by features like
37
+ # :expiration. It accepts an optional ttl parameter to maintain interface
38
+ # compatibility with the overriding implementations.
38
39
  #
39
- # @return [nil] Always returns nil. It's consistent in its laziness!
40
+ # @param ttl [Integer, nil] Time To Live in seconds (ignored in base implementation)
41
+ # @return [nil] Always returns nil
40
42
  #
41
- # @example Trying to teach an old dog new tricks
42
- # immortal_data.update_expiration(86400) # Nice try, but this data is here to stay!
43
+ # @note This is a no-op implementation. Classes that need expiration
44
+ # functionality should include the :expiration feature.
43
45
  #
44
- # @note This method is a no-op. It's like shouting into the void, but less echo-y.
45
- #
46
- def update_expiration(*)
47
- Familia.info "[update_expiration] Skipped for #{rediskey}. #{self.class} data is immortal!"
46
+ def update_expiration(ttl: nil)
47
+ Familia.ld "[update_expiration] Feature not enabled for #{self.class}. Key: #{rediskey} (caller: #{caller(1..1)})"
48
48
  nil
49
49
  end
50
50
 
@@ -6,6 +6,7 @@ require_relative '../../lib/redis_middleware'
6
6
  module Familia
7
7
  @uri = URI.parse 'redis://127.0.0.1'
8
8
  @redis_clients = {}
9
+ @redis_uri_by_class = {}
9
10
 
10
11
  # The Connection module provides Redis connection management for Familia.
11
12
  # It allows easy setup and access to Redis clients across different URIs.
@@ -25,17 +26,20 @@ module Familia
25
26
  # Establishes a connection to a Redis server.
26
27
  #
27
28
  # @param uri [String, URI, nil] The URI of the Redis server to connect to.
28
- # If nil, uses the default URI.
29
- # @return [Redis] The connected Redis client
29
+ # If nil, uses the default URI from `@redis_uri_by_class` or `Familia.uri`.
30
+ # @return [Redis] The connected Redis client.
31
+ # @raise [ArgumentError] If no URI is specified.
30
32
  # @example
31
33
  # Familia.connect('redis://localhost:6379')
32
34
  def connect(uri = nil)
33
35
  uri = URI.parse(uri) if uri.is_a?(String)
36
+ uri ||= @redis_uri_by_class[self]
34
37
  uri ||= Familia.uri
35
38
 
36
39
  raise ArgumentError, 'No URI specified' unless uri
37
40
 
38
41
  conf = uri.conf
42
+ @redis_uri_by_class[self] = uri.serverid
39
43
 
40
44
  if Familia.enable_redis_logging
41
45
  RedisLogger.logger = Familia.logger
@@ -49,6 +53,9 @@ module Familia
49
53
  end
50
54
 
51
55
  redis = Redis.new(conf)
56
+
57
+ # Close the existing connection if it exists
58
+ @redis_clients[uri.serverid].close if @redis_clients[uri.serverid]
52
59
  @redis_clients[uri.serverid] = redis
53
60
  end
54
61
 
@@ -72,6 +79,15 @@ module Familia
72
79
  @redis_clients[uri.serverid]
73
80
  end
74
81
 
82
+ # Retrieves the Redis client associated with the given class.
83
+ #
84
+ # @param klass [Class] The class for which to retrieve the Redis client.
85
+ # @return [Redis] The Redis client associated with the given class.
86
+ def redis_uri_by_class(klass)
87
+ uri = @redis_uri_by_class[klass]
88
+ connect(uri)
89
+ end
90
+
75
91
  # Sets the default URI for Redis connections.
76
92
  #
77
93
  # @param v [String, URI] The new default URI
@@ -30,4 +30,18 @@ module Familia
30
30
  "No client for #{uri.serverid}"
31
31
  end
32
32
  end
33
+
34
+ # Raised when attempting to refresh an object whose key doesn't exist in Redis
35
+ class KeyNotFoundError < Problem
36
+ attr_reader :key
37
+
38
+ def initialize(key)
39
+ @key = key
40
+ super
41
+ end
42
+
43
+ def message
44
+ "Key not found in Redis: #{key}"
45
+ end
46
+ end
33
47
  end
@@ -49,7 +49,7 @@ module Familia::Features
49
49
  # false otherwise.
50
50
  #
51
51
  # @example Setting an expiration of one day
52
- # object.update_expiration(86400)
52
+ # object.update_expiration(ttl: 86400)
53
53
  #
54
54
  # @note If TTL is set to zero, the expiration will be removed, making the
55
55
  # data persist indefinitely.
@@ -57,8 +57,19 @@ module Familia::Features
57
57
  # @raise [Familia::Problem] Raises an error if the TTL is not a non-negative
58
58
  # integer.
59
59
  #
60
- def update_expiration(ttl = nil)
60
+ def update_expiration(ttl: nil)
61
61
  ttl ||= self.ttl
62
+
63
+ if self.class.has_relations?
64
+ Familia.ld "[update_expiration] #{self.class} has relations: #{self.class.redis_types.keys}"
65
+ self.class.redis_types.each do |name, definition|
66
+ next if definition.opts[:ttl].nil?
67
+ obj = send(name)
68
+ Familia.ld "[update_expiration] Updating expiration for #{name} (#{obj.rediskey}) to #{ttl}"
69
+ obj.update_expiration(ttl: ttl)
70
+ end
71
+ end
72
+
62
73
  # It's important to raise exceptions here and not just log warnings. We
63
74
  # don't want to silently fail at setting expirations and cause data
64
75
  # retention issues (e.g. not removed in a timely fashion).
@@ -46,7 +46,7 @@ module Familia::Features
46
46
  end
47
47
 
48
48
  def qstamp(quantum = nil, pattern: nil, time: nil)
49
- self.class.qstamp(quantum || ttl, pattern: pattern, time: time)
49
+ self.class.qstamp(quantum || self.class.ttl, pattern: pattern, time: time)
50
50
  end
51
51
 
52
52
  extend ClassMethods
@@ -33,9 +33,6 @@ module Familia
33
33
  include Familia::Settings
34
34
  include Familia::Horreum::RelationsManagement
35
35
 
36
- attr_accessor :parent
37
- attr_writer :redis, :dump_method, :load_method
38
-
39
36
  # Returns the Redis connection for the class.
40
37
  #
41
38
  # This method retrieves the Redis connection instance for the class. If no
@@ -156,7 +153,7 @@ module Familia
156
153
  Familia.trace :FAST_WRITER, redis, "#{name}: #{val.inspect}", caller(1..1) if Familia.debug?
157
154
 
158
155
  # Convert the provided value to a format suitable for Redis storage.
159
- prepared = to_redis(val)
156
+ prepared = serialize_value(val)
160
157
  Familia.ld "[.fast_attribute!] #{name} val: #{val.class} prepared: #{prepared.class}"
161
158
 
162
159
  # Use the existing accessor method to set the attribute value.
@@ -196,6 +193,10 @@ module Familia
196
193
  @redis_types
197
194
  end
198
195
 
196
+ def has_relations?
197
+ @has_relations ||= false
198
+ end
199
+
199
200
  def db(v = nil)
200
201
  @db = v unless v.nil?
201
202
  @db || parent&.db
@@ -209,16 +210,20 @@ module Familia
209
210
  def all(suffix = nil)
210
211
  suffix ||= self.suffix
211
212
  # objects that could not be parsed will be nil
212
- keys(suffix).filter_map { |k| from_rediskey(k) }
213
+ keys(suffix).filter_map { |k| find_by_key(k) }
213
214
  end
214
215
 
215
216
  def any?(filter = '*')
216
- size(filter) > 0
217
+ matching_keys_count(filter) > 0
217
218
  end
218
219
 
219
- def size(filter = '*')
220
+ # Returns the number of Redis keys matching the given filter pattern
221
+ # @param filter [String] Redis key pattern to match (default: '*')
222
+ # @return [Integer] Number of matching keys
223
+ def matching_keys_count(filter = '*')
220
224
  redis.keys(rediskey(filter)).compact.size
221
225
  end
226
+ alias size matching_keys_count # For backwards compatibility
222
227
 
223
228
  def suffix(a = nil, &blk)
224
229
  @suffix = a || blk if a || !blk.nil?
@@ -312,17 +317,17 @@ module Familia
312
317
  # debugging.
313
318
  #
314
319
  # @example
315
- # User.from_rediskey("user:123") # Returns a User instance if it exists,
320
+ # User.find_by_key("user:123") # Returns a User instance if it exists,
316
321
  # nil otherwise
317
322
  #
318
- def from_rediskey(objkey)
323
+ def find_by_key(objkey)
319
324
  raise ArgumentError, 'Empty key' if objkey.to_s.empty?
320
325
 
321
326
  # We use a lower-level method here b/c we're working with the
322
327
  # full key and not just the identifier.
323
328
  does_exist = redis.exists(objkey).positive?
324
329
 
325
- Familia.ld "[.from_rediskey] #{self} from key #{objkey} (exists: #{does_exist})"
330
+ Familia.ld "[.find_by_key] #{self} from key #{objkey} (exists: #{does_exist})"
326
331
  Familia.trace :FROM_KEY, redis, objkey, caller(1..1) if Familia.debug?
327
332
 
328
333
  # This is the reason for calling exists first. We want to definitively
@@ -337,6 +342,7 @@ module Familia
337
342
 
338
343
  new(**obj)
339
344
  end
345
+ alias from_rediskey find_by_key # deprecated
340
346
 
341
347
  # Retrieves and instantiates an object from Redis using its identifier.
342
348
  #
@@ -347,7 +353,7 @@ module Familia
347
353
  # @return [Object, nil] An instance of the class if found, nil otherwise.
348
354
  #
349
355
  # This method constructs the full Redis key using the provided identifier
350
- # and suffix, then delegates to `from_rediskey` for the actual retrieval and
356
+ # and suffix, then delegates to `find_by_key` for the actual retrieval and
351
357
  # instantiation.
352
358
  #
353
359
  # It's a higher-level method that abstracts away the key construction,
@@ -355,19 +361,21 @@ module Familia
355
361
  # identifier.
356
362
  #
357
363
  # @example
358
- # User.from_identifier(123) # Equivalent to User.from_rediskey("user:123:object")
364
+ # User.find_by_id(123) # Equivalent to User.find_by_key("user:123:object")
359
365
  #
360
- def from_identifier(identifier, suffix = nil)
366
+ def find_by_id(identifier, suffix = nil)
361
367
  suffix ||= self.suffix
362
368
  return nil if identifier.to_s.empty?
363
369
 
364
370
  objkey = rediskey(identifier, suffix)
365
371
 
366
- Familia.ld "[.from_identifier] #{self} from key #{objkey})"
367
- Familia.trace :FROM_IDENTIFIER, Familia.redis(uri), objkey, caller(1..1).first if Familia.debug?
368
- from_rediskey objkey
372
+ Familia.ld "[.find_by_id] #{self} from key #{objkey})"
373
+ Familia.trace :FIND_BY_ID, Familia.redis(uri), objkey, caller(1..1).first if Familia.debug?
374
+ find_by_key objkey
369
375
  end
370
- alias load from_identifier
376
+ alias find find_by_id
377
+ alias load find_by_id # deprecated
378
+ alias from_identifier find_by_id # deprecated
371
379
 
372
380
  # Checks if an object with the given identifier exists in Redis.
373
381
  #
@@ -399,8 +407,10 @@ module Familia
399
407
  # @param suffix [Symbol, nil] The suffix to use in the Redis key (default: class suffix).
400
408
  # @return [Boolean] true if the object was successfully destroyed, false otherwise.
401
409
  #
402
- # This method constructs the full Redis key using the provided identifier and suffix,
403
- # then removes the corresponding key from Redis.
410
+ # This method is part of Familia's high-level object lifecycle management. While `delete!`
411
+ # operates directly on Redis keys, `destroy!` operates at the object level and is used for
412
+ # ORM-style operations. Use `destroy!` when removing complete objects from the system, and
413
+ # `delete!` when working directly with Redis keys.
404
414
  #
405
415
  # @example
406
416
  # User.destroy!(123) # Removes user:123:object from Redis
@@ -412,7 +422,7 @@ module Familia
412
422
  objkey = rediskey identifier, suffix
413
423
 
414
424
  ret = redis.del objkey
415
- Familia.trace :DELETED, redis, "#{objkey}: #{ret.inspect}", caller(1..1) if Familia.debug?
425
+ Familia.trace :DESTROY!, redis, "#{objkey} #{ret.inspect}", caller(1..1) if Familia.debug?
416
426
  ret.positive?
417
427
  end
418
428
 
@@ -428,7 +438,7 @@ module Familia
428
438
  # User.find # Returns all keys matching user:*:object
429
439
  # User.find('active') # Returns all keys matching user:*:active
430
440
  #
431
- def find(suffix = '*')
441
+ def find_keys(suffix = '*')
432
442
  redis.keys(rediskey('*', suffix)) || []
433
443
  end
434
444
 
@@ -18,11 +18,35 @@ module Familia
18
18
  #
19
19
  module Commands
20
20
 
21
+ def move(db)
22
+ redis.move rediskey, db
23
+ end
24
+
25
+ # Checks if the calling object's key exists in Redis and has a non-zero size.
26
+ #
27
+ # This method retrieves the Redis URI associated with the calling object's class
28
+ # using `Familia.redis_uri_by_class`. It then checks if the specified key exists
29
+ # in Redis and that its size is not zero. If debugging is enabled, it logs the
30
+ # existence check using `Familia.trace`.
31
+ #
32
+ # @return [Boolean] Returns `true` if the key exists in Redis and its size is not zero, otherwise `false`.
33
+ # @example
34
+ # if some_object.exists?
35
+ # # perform action
36
+ # end
21
37
  def exists?
22
- # Trace output comes from the class method
23
- self.class.exists? identifier, suffix
38
+ true_or_false = self.class.redis.exists?(rediskey) && !size.zero?
39
+ Familia.trace :EXISTS, redis, "#{key} #{true_or_false.inspect}", caller(1..1) if Familia.debug?
40
+ true_or_false
24
41
  end
25
42
 
43
+ # Returns the number of fields in the main object hash
44
+ # @return [Integer] number of fields
45
+ def field_count
46
+ redis.hlen rediskey
47
+ end
48
+ alias size field_count
49
+
26
50
  # Sets a timeout on key. After the timeout has expired, the key will
27
51
  # automatically be deleted. Returns 1 if the timeout was set, 0 if key
28
52
  # does not exist or the timeout could not be set.
@@ -33,20 +57,27 @@ module Familia
33
57
  redis.expire rediskey, ttl.to_i
34
58
  end
35
59
 
60
+ # Retrieves the remaining time to live (TTL) for the object's Redis key.
61
+ #
62
+ # This method accesses the ovjects Redis client to obtain the TTL of `rediskey`.
63
+ # If debugging is enabled, it logs the TTL retrieval operation using `Familia.trace`.
64
+ #
65
+ # @return [Integer] The TTL of the key in seconds. Returns -1 if the key does not exist
66
+ # or has no associated expire time.
36
67
  def realttl
37
68
  Familia.trace :REALTTL, redis, redisuri, caller(1..1) if Familia.debug?
38
69
  redis.ttl rediskey
39
70
  end
40
71
 
41
- # Deletes a field from the hash stored at the Redis key.
72
+ # Removes a field from the hash stored at the Redis key.
42
73
  #
43
- # @param field [String] The field to delete from the hash.
74
+ # @param field [String] The field to remove from the hash.
44
75
  # @return [Integer] The number of fields that were removed from the hash (0 or 1).
45
- # @note This method is destructive, as indicated by the bang (!).
46
- def hdel!(field)
76
+ def remove_field(field)
47
77
  Familia.trace :HDEL, redis, field, caller(1..1) if Familia.debug?
48
78
  redis.hdel rediskey, field
49
79
  end
80
+ alias remove remove_field # deprecated
50
81
 
51
82
  def redistype
52
83
  Familia.trace :REDISTYPE, redis, redisuri, caller(1..1) if Familia.debug?
@@ -59,6 +90,15 @@ module Familia
59
90
  redis.rename rediskey, newkey
60
91
  end
61
92
 
93
+ # Retrieves the prefix for the current instance by delegating to its class.
94
+ #
95
+ # @return [String] The prefix associated with the class of the current instance.
96
+ # @example
97
+ # instance.prefix
98
+ def prefix
99
+ self.class.prefix
100
+ end
101
+
62
102
  # For parity with RedisType#hgetall
63
103
  def hgetall
64
104
  Familia.trace :HGETALL, redis, redisuri, caller(1..1) if Familia.debug?
@@ -78,8 +118,10 @@ module Familia
78
118
  redis.hset rediskey, field, value
79
119
  end
80
120
 
81
- def hmset
82
- redis.hmset rediskey(suffix), self.to_h
121
+ def hmset(hsh={})
122
+ hsh ||= self.to_h
123
+ Familia.trace :HMSET, redis, hsh, caller(1..1) if Familia.debug?
124
+ redis.hmset rediskey(suffix), hsh
83
125
  end
84
126
 
85
127
  def hkeys
@@ -116,11 +158,6 @@ module Familia
116
158
  end
117
159
  alias decrement decr
118
160
 
119
- def hlen
120
- redis.hlen rediskey(suffix)
121
- end
122
- alias hlength hlen
123
-
124
161
  def hstrlen(field)
125
162
  redis.hstrlen rediskey(suffix), field
126
163
  end
@@ -131,12 +168,14 @@ module Familia
131
168
  end
132
169
  alias has_key? key?
133
170
 
171
+ # Deletes the entire Redis key
172
+ # @return [Boolean] true if the key was deleted, false otherwise
134
173
  def delete!
135
174
  Familia.trace :DELETE!, redis, redisuri, caller(1..1) if Familia.debug?
136
175
  ret = redis.del rediskey
137
176
  ret.positive?
138
177
  end
139
- protected :delete!
178
+ alias clear delete!
140
179
 
141
180
  end
142
181
 
@@ -16,6 +16,10 @@ module Familia
16
16
  # Call setup_relations_accessors to initialize the feature
17
17
  #
18
18
  module RelationsManagement
19
+ # A practical flag to indicate that a Horreum member has relations,
20
+ # not just theoretically but actually at least one list/haskey/etc.
21
+ @has_relations = nil
22
+
19
23
  def self.included(base)
20
24
  base.extend(ClassMethods)
21
25
  base.setup_relations_accessors
@@ -31,14 +35,17 @@ module Familia
31
35
 
32
36
  # Dynamically define instance-level relation methods
33
37
  #
34
- # Once defined, these methods can be used at the class-level of a
38
+ # Once defined, these methods can be used at the instance-level of a
35
39
  # Familia member to define *instance-level* relations to any of the
36
40
  # RedisType types (e.g. set, list, hash, etc).
37
41
  #
38
42
  define_method :"#{kind}" do |*args|
39
43
  name, opts = *args
44
+
45
+ # As log as we have at least one relation, we can set this flag.
46
+ @has_relations = true
47
+
40
48
  attach_instance_redis_object_relation name, klass, opts
41
- redis_types[name.to_s.to_sym]
42
49
  end
43
50
  define_method :"#{kind}?" do |name|
44
51
  obj = redis_types[name.to_s.to_sym]