familia 1.0.0.pre.rc6 → 1.1.0.pre.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +99 -41
  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 +8 -2
  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 +4 -4
  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
@@ -97,15 +97,8 @@ module Familia
97
97
  # connection. Don't worry, it puts everything back where it found it when it's done.
98
98
  #
99
99
  def transaction
100
- original_redis = self.redis
101
-
102
- begin
103
- redis.multi do |conn|
104
- self.instance_variable_set(:@redis, conn)
105
- yield(conn)
106
- end
107
- ensure
108
- self.redis = original_redis
100
+ redis.multi do |conn|
101
+ yield(conn)
109
102
  end
110
103
  end
111
104
 
@@ -130,15 +123,15 @@ module Familia
130
123
 
131
124
  # Update our object's life story
132
125
  self.key ||= self.identifier
133
- self.updated = Familia.now.to_i
134
- self.created ||= Familia.now.to_i
126
+ self.created ||= Familia.now.to_i if respond_to?(:created)
127
+ self.updated = Familia.now.to_i if respond_to?(:updated)
135
128
 
136
129
  # Commit our tale to the Redis chronicles
137
130
  #
138
131
  # e.g. `ret` # => MultiResult.new(true, ["OK", "OK"])
139
132
  ret = commit_fields(update_expiration: update_expiration)
140
133
 
141
- Familia.ld "[save] #{self.class} #{rediskey} #{ret}"
134
+ Familia.ld "[save] #{self.class} #{rediskey} #{ret} (update_expiration: #{update_expiration})"
142
135
 
143
136
  # Did Redis accept our offering?
144
137
  ret.successful?
@@ -174,6 +167,10 @@ module Familia
174
167
  # It executes a transaction that includes setting field values and,
175
168
  # if applicable, updating the expiration time.
176
169
  #
170
+ # @param update_expiration [Boolean] Whether to update the expiration time
171
+ # of the Redis key. This is true by default, but can be disabled if you
172
+ # don't want to mess with the cosmic balance of your key's lifespan.
173
+ #
177
174
  # @return [MultiResult] A mystical object containing:
178
175
  # - success: A boolean indicating if all Redis commands succeeded
179
176
  # - results: An array of strings, cryptic messages from the Redis gods
@@ -213,13 +210,12 @@ module Familia
213
210
  def commit_fields update_expiration: true
214
211
  Familia.ld "[commit_fields1] #{self.class} #{rediskey} #{to_h} (update_expiration: #{update_expiration})"
215
212
  command_return_values = transaction do |conn|
216
- hmset
217
-
218
- # Only classes that have the expiration ferature enabled will
219
- # actually set an expiration time on their keys. Otherwise
220
- # this will be a no-op that simply logs the attempt.
221
- self.update_expiration if update_expiration
213
+ conn.hmset rediskey(suffix), self.to_h # using the prepared connection
222
214
  end
215
+ # Only classes that have the expiration ferature enabled will
216
+ # actually set an expiration time on their keys. Otherwise
217
+ # this will be a no-op that simply logs the attempt.
218
+ self.update_expiration(ttl: nil) if update_expiration
223
219
 
224
220
  # The acceptable redis command return values are defined in the
225
221
  # Horreum class. This is to ensure that all commands return values
@@ -256,6 +252,11 @@ module Familia
256
252
  # rocky.destroy!
257
253
  # # => *poof* Rocky is no more. A moment of silence, please.
258
254
  #
255
+ # This method is part of Familia's high-level object lifecycle management. While `delete!`
256
+ # operates directly on Redis keys, `destroy!` operates at the object level and is used for
257
+ # ORM-style operations. Use `destroy!` when removing complete objects from the system, and
258
+ # `delete!` when working directly with Redis keys.
259
+ #
259
260
  # @note If debugging is enabled, this method will leave a trace of its
260
261
  # destructive path, like breadcrumbs for future data archaeologists.
261
262
  #
@@ -276,14 +277,19 @@ module Familia
276
277
  # Gone quicker than cake at a hobbit's birthday party. Unsaved spells
277
278
  # will definitely be forgotten.
278
279
  #
279
- # @return What do you get for this daring act of digital amnesia? A shiny
280
+ # @return [void] What do you get for this daring act of digital amnesia? A shiny
280
281
  # list of all the brain bits that got a makeover!
281
282
  #
282
283
  # Remember: In the game of Redis-Refresh, you win or you... well, you
283
284
  # always win, but sometimes you forget why you played in the first place.
284
285
  #
286
+ # @raise [Familia::KeyNotFoundError] If the Redis key does not exist.
287
+ #
288
+ # @example
289
+ # object.refresh!
285
290
  def refresh!
286
291
  Familia.trace :REFRESH, redis, redisuri, caller(1..1) if Familia.debug?
292
+ raise Familia::KeyNotFoundError, rediskey unless redis.exists(rediskey)
287
293
  fields = hgetall
288
294
  Familia.ld "[refresh!] #{self.class} #{rediskey} #{fields.keys}"
289
295
  optimistic_refresh(**fields)
@@ -303,6 +309,8 @@ module Familia
303
309
  # @return [self] Your object, freshly bathed in Redis waters, ready
304
310
  # to dance with more methods in a conga line of Ruby joy!
305
311
  #
312
+ # @raise [Familia::KeyNotFoundError] If the Redis key does not exist.
313
+ #
306
314
  def refresh
307
315
  refresh!
308
316
  self
@@ -326,7 +334,7 @@ module Familia
326
334
  def to_h
327
335
  self.class.fields.inject({}) do |hsh, field|
328
336
  val = send(field)
329
- prepared = to_redis(val)
337
+ prepared = serialize_value(val)
330
338
  Familia.ld " [to_h] field: #{field} val: #{val.class} prepared: #{prepared.class}"
331
339
  hsh[field] = prepared
332
340
  hsh
@@ -351,7 +359,7 @@ module Familia
351
359
  def to_a
352
360
  self.class.fields.map do |field|
353
361
  val = send(field)
354
- prepared = to_redis(val)
362
+ prepared = serialize_value(val)
355
363
  Familia.ld " [to_a] field: #{field} val: #{val.class} prepared: #{prepared.class}"
356
364
  prepared
357
365
  end
@@ -392,7 +400,7 @@ module Familia
392
400
  #
393
401
  # @return [String] The transformed, Redis-ready value.
394
402
  #
395
- def to_redis(val)
403
+ def serialize_value(val)
396
404
  prepared = Familia.distinguisher(val, strict_values: false)
397
405
 
398
406
  if prepared.nil? && val.respond_to?(dump_method)
@@ -400,11 +408,12 @@ module Familia
400
408
  end
401
409
 
402
410
  if prepared.nil?
403
- Familia.ld "[#{self.class}#to_redis] nil returned for #{self.class}##{name}"
411
+ Familia.ld "[#{self.class}#serialize_value] nil returned for #{self.class}##{name}"
404
412
  end
405
413
 
406
414
  prepared
407
415
  end
416
+ alias to_redis serialize_value
408
417
 
409
418
  end
410
419
  # End of Serialization module
@@ -54,6 +54,10 @@ module Familia
54
54
  # but not existing instances of the class.
55
55
  #
56
56
  class << self
57
+ attr_accessor :parent
58
+ attr_writer :redis, :dump_method, :load_method
59
+ attr_reader :has_relations
60
+
57
61
  # Extends ClassMethods to subclasses and tracks Familia members
58
62
  def inherited(member)
59
63
  Familia.trace :HORREUM, nil, "Welcome #{member} to the family", caller(1..1) if Familia.debug?
@@ -82,7 +86,7 @@ module Familia
82
86
  # Define the 'key' field for this class
83
87
  # This approach allows flexibility in how identifiers are generated
84
88
  # while ensuring each object has a consistent way to be referenced
85
- self.class.field :key # , default: -> { identifier }
89
+ self.class.field :key
86
90
  end
87
91
 
88
92
  # If there are positional arguments, they should be the field
@@ -142,9 +146,11 @@ module Familia
142
146
  #
143
147
  opts[:parent] = self # unless opts.key(:parent)
144
148
 
149
+ suffix_override = opts.fetch(:suffix, name)
150
+
145
151
  # Instantiate the RedisType object and below we store it in
146
152
  # an instance variable.
147
- redis_type = klass.new name, opts
153
+ redis_type = klass.new suffix_override, opts
148
154
 
149
155
  # Freezes the redis_type, making it immutable.
150
156
  # This ensures the object's state remains consistent and prevents any modifications,
@@ -22,11 +22,14 @@ class Familia::RedisType
22
22
  redis.type rediskey
23
23
  end
24
24
 
25
+ # Deletes the entire Redis key
26
+ # @return [Boolean] true if the key was deleted, false otherwise
25
27
  def delete!
26
- redis.del rediskey
28
+ Familia.trace :DELETE!, redis, redisuri, caller(1..1) if Familia.debug?
29
+ ret = redis.del rediskey
30
+ ret.positive?
27
31
  end
28
32
  alias clear delete!
29
- alias del delete!
30
33
 
31
34
  def exists?
32
35
  redis.exists(rediskey) && !size.zero?
@@ -17,16 +17,16 @@ class Familia::RedisType
17
17
  # serialization.
18
18
  #
19
19
  # @example With a class option
20
- # to_redis(User.new(name: "Cloe"), strict_values: false) #=> '{"name":"Cloe"}'
20
+ # serialize_value(User.new(name: "Cloe"), strict_values: false) #=> '{"name":"Cloe"}'
21
21
  #
22
22
  # @example Without a class option
23
- # to_redis(123) #=> "123"
24
- # to_redis("hello") #=> "hello"
23
+ # serialize_value(123) #=> "123"
24
+ # serialize_value("hello") #=> "hello"
25
25
  #
26
26
  # @raise [Familia::HighRiskFactor] If serialization fails under strict
27
27
  # mode.
28
28
  #
29
- def to_redis(val, strict_values: true)
29
+ def serialize_value(val, strict_values: true)
30
30
  prepared = nil
31
31
 
32
32
  Familia.trace :TOREDIS, redis, "#{val}<#{val.class}|#{opts[:class]}>", caller(1..1) if Familia.debug?
@@ -44,24 +44,26 @@ class Familia::RedisType
44
44
 
45
45
  Familia.trace :TOREDIS, redis, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>", caller(1..1) if Familia.debug?
46
46
 
47
- Familia.warn "[#{self.class}\#to_redis] nil returned for #{opts[:class]}\##{name}" if prepared.nil?
47
+ Familia.warn "[#{self.class}\#serialize_value] nil returned for #{opts[:class]}\##{name}" if prepared.nil?
48
48
  prepared
49
49
  end
50
+ alias to_redis serialize_value
50
51
 
51
52
  # Deserializes multiple values from Redis, removing nil values.
52
53
  #
53
54
  # @param values [Array<String>] The values to deserialize.
54
55
  # @return [Array<Object>] Deserialized objects, with nil values removed.
55
56
  #
56
- # @see #multi_from_redis_with_nil
57
+ # @see #deserialize_values_with_nil
57
58
  #
58
- def multi_from_redis(*values)
59
+ def deserialize_values(*values)
59
60
  # Avoid using compact! here. Using compact! as the last expression in the
60
61
  # method can unintentionally return nil if no changes are made, which is
61
62
  # not desirable. Instead, use compact to ensure the method returns the
62
63
  # expected value.
63
- multi_from_redis_with_nil(*values).compact
64
+ deserialize_values_with_nil(*values).compact
64
65
  end
66
+ alias from_redis deserialize_values
65
67
 
66
68
  # Deserializes multiple values from Redis, preserving nil values.
67
69
  #
@@ -75,11 +77,8 @@ class Familia::RedisType
75
77
  # class's load method. If deserialization fails for a value, it's
76
78
  # replaced with nil.
77
79
  #
78
- # NOTE: `multi` in this method name refers to multiple values from
79
- # redis and not the Redis server MULTI command.
80
- #
81
- def multi_from_redis_with_nil(*values)
82
- Familia.ld "multi_from_redis: (#{@opts}) #{values}"
80
+ def deserialize_values_with_nil(*values)
81
+ Familia.ld "deserialize_values: (#{@opts}) #{values}"
83
82
  return [] if values.empty?
84
83
  return values.flatten unless @opts[:class]
85
84
 
@@ -92,7 +91,7 @@ class Familia::RedisType
92
91
 
93
92
  val = @opts[:class].send load_method, obj
94
93
  if val.nil?
95
- Familia.ld "[#{self.class}\#multi_from_redis] nil returned for #{@opts[:class]}\##{name}"
94
+ Familia.ld "[#{self.class}\#deserialize_values] nil returned for #{@opts[:class]}\##{name}"
96
95
  end
97
96
 
98
97
  val
@@ -105,6 +104,7 @@ class Familia::RedisType
105
104
 
106
105
  values
107
106
  end
107
+ alias from_redis_with_nil deserialize_values_with_nil
108
108
 
109
109
  # Deserializes a single value from Redis.
110
110
  #
@@ -120,13 +120,14 @@ class Familia::RedisType
120
120
  # deserialization options that RedisType supports. It uses to_redis
121
121
  # for serialization since everything becomes a string in Redis.
122
122
  #
123
- def from_redis(val)
123
+ def deserialize_value(val)
124
124
  return @opts[:default] if val.nil?
125
125
  return val unless @opts[:class]
126
126
 
127
- ret = multi_from_redis val
127
+ ret = deserialize_values val
128
128
  ret&.first # return the object or nil
129
129
  end
130
+ alias from_redis deserialize_value
130
131
  end
131
132
 
132
133
  end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Familia
4
+ class HashKey < RedisType
5
+ # Returns the number of fields in the hash
6
+ # @return [Integer] number of fields
7
+ def field_count
8
+ redis.hlen rediskey
9
+ end
10
+ alias size field_count
11
+
12
+ def empty?
13
+ field_count.zero?
14
+ end
15
+
16
+ # +return+ [Integer] Returns 1 if the field is new and added, 0 if the
17
+ # field already existed and the value was updated.
18
+ def []=(field, val)
19
+ ret = redis.hset rediskey, field, serialize_value(val)
20
+ update_expiration
21
+ ret
22
+ rescue TypeError => e
23
+ Familia.le "[hset]= #{e.message}"
24
+ Familia.ld "[hset]= #{rediskey} #{field}=#{val}" if Familia.debug
25
+ echo :hset, caller(1..1).first if Familia.debug # logs via echo to redis and back
26
+ klass = val.class
27
+ msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{rediskey}"
28
+ raise e.class, msg
29
+ end
30
+ alias put []=
31
+ alias store []=
32
+
33
+ def [](field)
34
+ deserialize_value redis.hget(rediskey, field)
35
+ end
36
+ alias get []
37
+
38
+ def fetch(field, default = nil)
39
+ ret = self[field]
40
+ if ret.nil?
41
+ raise IndexError, "No such index for: #{field}" if default.nil?
42
+
43
+ default
44
+ else
45
+ ret
46
+ end
47
+ end
48
+
49
+ def keys
50
+ redis.hkeys rediskey
51
+ end
52
+
53
+ def values
54
+ redis.hvals(rediskey).map { |v| deserialize_value v }
55
+ end
56
+
57
+ def hgetall
58
+ redis.hgetall(rediskey).each_with_object({}) do |(k,v), ret|
59
+ ret[k] = deserialize_value v
60
+ end
61
+ end
62
+ alias all hgetall
63
+
64
+ def key?(field)
65
+ redis.hexists rediskey, field
66
+ end
67
+ alias has_key? key?
68
+ alias include? key?
69
+ alias member? key?
70
+
71
+ # Removes a field from the hash
72
+ # @param field [String] The field to remove
73
+ # @return [Integer] The number of fields that were removed (0 or 1)
74
+ def remove_field(field)
75
+ redis.hdel rediskey, field
76
+ end
77
+ alias remove remove_field # deprecated
78
+
79
+ def increment(field, by = 1)
80
+ redis.hincrby(rediskey, field, by).to_i
81
+ end
82
+ alias incr increment
83
+ alias incrby increment
84
+
85
+ def decrement(field, by = 1)
86
+ increment field, -by
87
+ end
88
+ alias decr decrement
89
+ alias decrby decrement
90
+
91
+ def update(hsh = {})
92
+ raise ArgumentError, 'Argument to bulk_set must be a hash' unless hsh.is_a?(Hash)
93
+
94
+ data = hsh.inject([]) { |ret, pair| ret << [pair[0], serialize_value(pair[1])] }.flatten
95
+
96
+ ret = redis.hmset(rediskey, *data)
97
+ update_expiration
98
+ ret
99
+ end
100
+ alias merge! update
101
+
102
+ def values_at *fields
103
+ elements = redis.hmget(rediskey, *fields.flatten.compact)
104
+ deserialize_values(*elements)
105
+ end
106
+
107
+ # The Great Redis Refresh-o-matic 3000 for HashKey!
108
+ #
109
+ # This method performs a complete refresh of the hash's state from Redis.
110
+ # It's like giving your hash a memory transfusion - out with the old state,
111
+ # in with the fresh data straight from Redis!
112
+ #
113
+ # @note This operation is atomic - it either succeeds completely or fails
114
+ # safely. Any unsaved changes to the hash will be overwritten.
115
+ #
116
+ # @return [void] Returns nothing, but your hash will be sparkling clean
117
+ # with all its fields synchronized with Redis.
118
+ #
119
+ # @raise [Familia::KeyNotFoundError] If the Redis key for this hash no
120
+ # longer exists. Time travelers beware!
121
+ #
122
+ # @example Basic usage
123
+ # my_hash.refresh! # ZAP! Fresh data loaded
124
+ #
125
+ # @example With error handling
126
+ # begin
127
+ # my_hash.refresh!
128
+ # rescue Familia::KeyNotFoundError
129
+ # puts "Oops! Our hash seems to have vanished into the Redis void!"
130
+ # end
131
+ def refresh!
132
+ Familia.trace :REFRESH, redis, redisuri, caller(1..1) if Familia.debug?
133
+ raise Familia::KeyNotFoundError, rediskey unless redis.exists(rediskey)
134
+
135
+ fields = hgetall
136
+ Familia.ld "[refresh!] #{self.class} #{rediskey} #{fields.keys}"
137
+
138
+ # For HashKey, we update by merging the fresh data
139
+ update(fields)
140
+ end
141
+
142
+ # The friendly neighborhood refresh method!
143
+ #
144
+ # This method is like refresh! but with better manners - it returns self
145
+ # so you can chain it with other methods. It's perfect for when you want
146
+ # to refresh your hash and immediately do something with it.
147
+ #
148
+ # @return [self] Returns the refreshed hash, ready for more adventures!
149
+ #
150
+ # @raise [Familia::KeyNotFoundError] If the Redis key does not exist.
151
+ # The hash must exist in Redis-land for this to work!
152
+ #
153
+ # @example Refresh and chain
154
+ # my_hash.refresh.keys # Refresh and get all keys
155
+ # my_hash.refresh['field'] # Refresh and get a specific field
156
+ #
157
+ # @see #refresh! For the heavy lifting behind the scenes
158
+ def refresh
159
+ refresh!
160
+ self
161
+ end
162
+
163
+ Familia::RedisType.register self, :hash # legacy, deprecated
164
+ Familia::RedisType.register self, :hashkey
165
+ end
166
+ end
@@ -2,18 +2,21 @@
2
2
 
3
3
  module Familia
4
4
  class List < RedisType
5
- def size
5
+
6
+ # Returns the number of elements in the list
7
+ # @return [Integer] number of elements
8
+ def element_count
6
9
  redis.llen rediskey
7
10
  end
8
- alias length size
11
+ alias size element_count
9
12
 
10
13
  def empty?
11
- size.zero?
14
+ element_count.zero?
12
15
  end
13
16
 
14
17
  def push *values
15
18
  echo :push, caller(1..1).first if Familia.debug
16
- values.flatten.compact.each { |v| redis.rpush rediskey, to_redis(v) }
19
+ values.flatten.compact.each { |v| redis.rpush rediskey, serialize_value(v) }
17
20
  redis.ltrim rediskey, -@opts[:maxlength], -1 if @opts[:maxlength]
18
21
  update_expiration
19
22
  self
@@ -26,7 +29,7 @@ module Familia
26
29
  alias add <<
27
30
 
28
31
  def unshift *values
29
- values.flatten.compact.each { |v| redis.lpush rediskey, to_redis(v) }
32
+ values.flatten.compact.each { |v| redis.lpush rediskey, serialize_value(v) }
30
33
  # TODO: test maxlength
31
34
  redis.ltrim rediskey, 0, @opts[:maxlength] - 1 if @opts[:maxlength]
32
35
  update_expiration
@@ -35,11 +38,11 @@ module Familia
35
38
  alias prepend unshift
36
39
 
37
40
  def pop
38
- from_redis redis.rpop(rediskey)
41
+ deserialize_value redis.rpop(rediskey)
39
42
  end
40
43
 
41
44
  def shift
42
- from_redis redis.lpop(rediskey)
45
+ deserialize_value redis.lpop(rediskey)
43
46
  end
44
47
 
45
48
  def [](idx, count = nil)
@@ -57,16 +60,18 @@ module Familia
57
60
  end
58
61
  alias slice []
59
62
 
60
- def delete(v, count = 0)
61
- redis.lrem rediskey, count, to_redis(v)
63
+ # Removes elements equal to value from the list
64
+ # @param value The value to remove
65
+ # @param count [Integer] Number of elements to remove (0 means all)
66
+ # @return [Integer] The number of removed elements
67
+ def remove_element(value, count = 0)
68
+ redis.lrem rediskey, count, serialize_value(value)
62
69
  end
63
- alias remove delete
64
- alias rem delete
65
- alias del delete
70
+ alias remove remove_element
66
71
 
67
72
  def range(sidx = 0, eidx = -1)
68
73
  elements = rangeraw sidx, eidx
69
- multi_from_redis(*elements)
74
+ deserialize_values(*elements)
70
75
  end
71
76
 
72
77
  def rangeraw(sidx = 0, eidx = -1)
@@ -119,7 +124,7 @@ module Familia
119
124
  end
120
125
 
121
126
  def at(idx)
122
- from_redis redis.lindex(rediskey, idx)
127
+ deserialize_value redis.lindex(rediskey, idx)
123
128
  end
124
129
 
125
130
  def first
@@ -2,13 +2,16 @@
2
2
 
3
3
  module Familia
4
4
  class SortedSet < RedisType
5
- def size
5
+
6
+ # Returns the number of elements in the sorted set
7
+ # @return [Integer] number of elements
8
+ def element_count
6
9
  redis.zcard rediskey
7
10
  end
8
- alias length size
11
+ alias size element_count
9
12
 
10
13
  def empty?
11
- size.zero?
14
+ element_count.zero?
12
15
  end
13
16
 
14
17
  # Adds a new element to the sorted set with the current timestamp as the
@@ -44,13 +47,13 @@ module Familia
44
47
  end
45
48
 
46
49
  def add(score, val)
47
- ret = redis.zadd rediskey, score, to_redis(val)
50
+ ret = redis.zadd rediskey, score, serialize_value(val)
48
51
  update_expiration
49
52
  ret
50
53
  end
51
54
 
52
55
  def score(val)
53
- ret = redis.zscore rediskey, to_redis(val, strict_values: false)
56
+ ret = redis.zscore rediskey, serialize_value(val, strict_values: false)
54
57
  ret&.to_f
55
58
  end
56
59
  alias [] score
@@ -63,20 +66,20 @@ module Familia
63
66
 
64
67
  # rank of member +v+ when ordered lowest to highest (starts at 0)
65
68
  def rank(v)
66
- ret = redis.zrank rediskey, to_redis(v, strict_values: false)
69
+ ret = redis.zrank rediskey, serialize_value(v, strict_values: false)
67
70
  ret&.to_i
68
71
  end
69
72
 
70
73
  # rank of member +v+ when ordered highest to lowest (starts at 0)
71
74
  def revrank(v)
72
- ret = redis.zrevrank rediskey, to_redis(v, strict_values: false)
75
+ ret = redis.zrevrank rediskey, serialize_value(v, strict_values: false)
73
76
  ret&.to_i
74
77
  end
75
78
 
76
79
  def members(count = -1, opts = {})
77
80
  count -= 1 if count.positive?
78
81
  elements = membersraw count, opts
79
- multi_from_redis(*elements)
82
+ deserialize_values(*elements)
80
83
  end
81
84
  alias to_a members
82
85
  alias all members
@@ -89,7 +92,7 @@ module Familia
89
92
  def revmembers(count = -1, opts = {})
90
93
  count -= 1 if count.positive?
91
94
  elements = revmembersraw count, opts
92
- multi_from_redis(*elements)
95
+ deserialize_values(*elements)
93
96
  end
94
97
 
95
98
  def revmembersraw(count = -1, opts = {})
@@ -132,7 +135,7 @@ module Familia
132
135
  def range(sidx, eidx, opts = {})
133
136
  echo :range, caller(1..1).first if Familia.debug
134
137
  elements = rangeraw(sidx, eidx, opts)
135
- multi_from_redis(*elements)
138
+ deserialize_values(*elements)
136
139
  end
137
140
 
138
141
  def rangeraw(sidx, eidx, opts = {})
@@ -149,7 +152,7 @@ module Familia
149
152
  def revrange(sidx, eidx, opts = {})
150
153
  echo :revrange, caller(1..1).first if Familia.debug
151
154
  elements = revrangeraw(sidx, eidx, opts)
152
- multi_from_redis(*elements)
155
+ deserialize_values(*elements)
153
156
  end
154
157
 
155
158
  def revrangeraw(sidx, eidx, opts = {})
@@ -160,7 +163,7 @@ module Familia
160
163
  def rangebyscore(sscore, escore, opts = {})
161
164
  echo :rangebyscore, caller(1..1).first if Familia.debug
162
165
  elements = rangebyscoreraw(sscore, escore, opts)
163
- multi_from_redis(*elements)
166
+ deserialize_values(*elements)
164
167
  end
165
168
 
166
169
  def rangebyscoreraw(sscore, escore, opts = {})
@@ -172,7 +175,7 @@ module Familia
172
175
  def revrangebyscore(sscore, escore, opts = {})
173
176
  echo :revrangebyscore, caller(1..1).first if Familia.debug
174
177
  elements = revrangebyscoreraw(sscore, escore, opts)
175
- multi_from_redis(*elements)
178
+ deserialize_values(*elements)
176
179
  end
177
180
 
178
181
  def revrangebyscoreraw(sscore, escore, opts = {})
@@ -201,18 +204,19 @@ module Familia
201
204
  alias decr decrement
202
205
  alias decrby decrement
203
206
 
204
- def delete(val)
205
- Familia.trace :DELETE, redis, "#{val}<#{val.class}>", caller(1..1) if Familia.debug?
207
+ # Removes a member from the sorted set
208
+ # @param value The value to remove from the sorted set
209
+ # @return [Integer] The number of members that were removed (0 or 1)
210
+ def remove_element(value)
211
+ Familia.trace :REMOVE_ELEMENT, redis, "#{value}<#{value.class}>", caller(1..1) if Familia.debug?
206
212
  # We use `strict_values: false` here to allow for the deletion of values
207
213
  # that are in the sorted set. If it's a horreum object, the value is
208
214
  # the identifier and not a serialized version of the object. So either
209
215
  # the value exists in the sorted set or it doesn't -- we don't need to
210
216
  # raise an error if it's not found.
211
- redis.zrem rediskey, to_redis(val, strict_values: false)
217
+ redis.zrem rediskey, serialize_value(value, strict_values: false)
212
218
  end
213
- alias remove delete
214
- alias rem delete
215
- alias del delete
219
+ alias remove remove_element # deprecated
216
220
 
217
221
  def at(idx)
218
222
  range(idx, idx).first