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.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/Gemfile.lock +3 -1
- data/README.md +5 -5
- data/VERSION.yml +2 -2
- data/familia.gemspec +1 -2
- data/lib/familia/base.rb +11 -11
- data/lib/familia/connection.rb +18 -2
- data/lib/familia/errors.rb +14 -0
- data/lib/familia/features/expiration.rb +13 -2
- data/lib/familia/features/quantization.rb +1 -1
- data/lib/familia/horreum/class_methods.rb +31 -21
- data/lib/familia/horreum/commands.rb +53 -14
- data/lib/familia/horreum/relations_management.rb +9 -2
- data/lib/familia/horreum/serialization.rb +32 -23
- data/lib/familia/horreum.rb +5 -1
- data/lib/familia/redistype/commands.rb +5 -2
- data/lib/familia/redistype/serialization.rb +17 -16
- data/lib/familia/redistype/types/hashkey.rb +166 -0
- data/lib/familia/{types → redistype/types}/list.rb +19 -14
- data/lib/familia/{types → redistype/types}/sorted_set.rb +23 -19
- data/lib/familia/{types → redistype/types}/string.rb +8 -6
- data/lib/familia/{types → redistype/types}/unsorted_set.rb +16 -12
- data/lib/familia/redistype.rb +19 -9
- data/lib/familia.rb +5 -1
- data/try/10_familia_try.rb +1 -1
- data/try/20_redis_type_try.rb +1 -1
- data/try/21_redis_type_zset_try.rb +1 -1
- data/try/22_redis_type_set_try.rb +1 -1
- data/try/23_redis_type_list_try.rb +2 -2
- data/try/24_redis_type_string_try.rb +3 -3
- data/try/25_redis_type_hash_try.rb +1 -1
- data/try/26_redis_bool_try.rb +2 -2
- data/try/27_redis_horreum_try.rb +2 -2
- data/try/30_familia_object_try.rb +8 -5
- data/try/40_customer_try.rb +6 -6
- metadata +15 -15
- 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
|
-
|
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.
|
134
|
-
self.
|
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 =
|
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 =
|
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
|
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}#
|
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
|
data/lib/familia/horreum.rb
CHANGED
@@ -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
|
89
|
+
self.class.field :key
|
86
90
|
end
|
87
91
|
|
88
92
|
# If there are positional arguments, they should be the field
|
@@ -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.
|
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
|
-
#
|
20
|
+
# serialize_value(User.new(name: "Cloe"), strict_values: false) #=> '{"name":"Cloe"}'
|
21
21
|
#
|
22
22
|
# @example Without a class option
|
23
|
-
#
|
24
|
-
#
|
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
|
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}\#
|
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 #
|
57
|
+
# @see #deserialize_values_with_nil
|
57
58
|
#
|
58
|
-
def
|
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
|
-
|
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
|
-
|
79
|
-
|
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}\#
|
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
|
123
|
+
def deserialize_value(val)
|
124
124
|
return @opts[:default] if val.nil?
|
125
125
|
return val unless @opts[:class]
|
126
126
|
|
127
|
-
ret =
|
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
|
-
|
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
|
11
|
+
alias size element_count
|
9
12
|
|
10
13
|
def empty?
|
11
|
-
|
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,
|
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,
|
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
|
-
|
41
|
+
deserialize_value redis.rpop(rediskey)
|
39
42
|
end
|
40
43
|
|
41
44
|
def shift
|
42
|
-
|
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
|
-
|
61
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
11
|
+
alias size element_count
|
9
12
|
|
10
13
|
def empty?
|
11
|
-
|
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,
|
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,
|
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,
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
205
|
-
|
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,
|
217
|
+
redis.zrem rediskey, serialize_value(value, strict_values: false)
|
212
218
|
end
|
213
|
-
alias remove
|
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
|