familia 0.10.2 → 1.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.pre-commit-config.yaml +1 -1
  4. data/.rubocop.yml +75 -0
  5. data/.rubocop_todo.yml +63 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +47 -15
  8. data/README.md +11 -12
  9. data/VERSION.yml +4 -3
  10. data/familia.gemspec +18 -13
  11. data/lib/familia/base.rb +33 -0
  12. data/lib/familia/connection.rb +87 -0
  13. data/lib/familia/core_ext.rb +119 -124
  14. data/lib/familia/errors.rb +33 -0
  15. data/lib/familia/features/api_version.rb +19 -0
  16. data/lib/familia/features/atomic_saves.rb +8 -0
  17. data/lib/familia/features/quantizer.rb +35 -0
  18. data/lib/familia/features/safe_dump.rb +175 -0
  19. data/lib/familia/features.rb +51 -0
  20. data/lib/familia/horreum/class_methods.rb +240 -0
  21. data/lib/familia/horreum/commands.rb +59 -0
  22. data/lib/familia/horreum/relations_management.rb +141 -0
  23. data/lib/familia/horreum/serialization.rb +154 -0
  24. data/lib/familia/horreum/settings.rb +63 -0
  25. data/lib/familia/horreum/utils.rb +43 -0
  26. data/lib/familia/horreum.rb +198 -0
  27. data/lib/familia/logging.rb +249 -0
  28. data/lib/familia/redistype/commands.rb +56 -0
  29. data/lib/familia/redistype/serialization.rb +110 -0
  30. data/lib/familia/redistype.rb +185 -0
  31. data/lib/familia/settings.rb +38 -0
  32. data/lib/familia/types/hashkey.rb +108 -0
  33. data/lib/familia/types/list.rb +155 -0
  34. data/lib/familia/types/sorted_set.rb +234 -0
  35. data/lib/familia/types/string.rb +115 -0
  36. data/lib/familia/types/unsorted_set.rb +123 -0
  37. data/lib/familia/utils.rb +129 -0
  38. data/lib/familia/version.rb +25 -0
  39. data/lib/familia.rb +56 -161
  40. data/lib/redis_middleware.rb +109 -0
  41. data/try/00_familia_try.rb +5 -4
  42. data/try/10_familia_try.rb +21 -17
  43. data/try/20_redis_type_try.rb +67 -0
  44. data/try/{21_redis_object_zset_try.rb → 21_redis_type_zset_try.rb} +2 -2
  45. data/try/{22_redis_object_set_try.rb → 22_redis_type_set_try.rb} +2 -2
  46. data/try/{23_redis_object_list_try.rb → 23_redis_type_list_try.rb} +2 -2
  47. data/try/{24_redis_object_string_try.rb → 24_redis_type_string_try.rb} +6 -6
  48. data/try/{25_redis_object_hash_try.rb → 25_redis_type_hash_try.rb} +3 -3
  49. data/try/26_redis_bool_try.rb +10 -6
  50. data/try/27_redis_horreum_try.rb +40 -0
  51. data/try/30_familia_object_try.rb +21 -20
  52. data/try/35_feature_safedump_try.rb +83 -0
  53. data/try/40_customer_try.rb +140 -0
  54. data/try/41_customer_safedump_try.rb +86 -0
  55. data/try/test_helpers.rb +186 -0
  56. metadata +50 -47
  57. data/lib/familia/helpers.rb +0 -70
  58. data/lib/familia/object.rb +0 -533
  59. data/lib/familia/redisobject.rb +0 -1017
  60. data/lib/familia/test_helpers.rb +0 -40
  61. data/lib/familia/tools.rb +0 -67
  62. data/try/20_redis_object_try.rb +0 -44
@@ -0,0 +1,185 @@
1
+ # rubocop:disable all
2
+
3
+ require_relative 'redistype/commands'
4
+ require_relative 'redistype/serialization'
5
+
6
+ module Familia
7
+
8
+ # RedisType - Base class for Redis data type wrappers
9
+ #
10
+ # This class provides common functionality for various Redis data types
11
+ # such as String, List, Set, SortedSet, and HashKey.
12
+ #
13
+ # @abstract Subclass and implement Redis data type specific methods
14
+ class RedisType
15
+ include Familia::Base
16
+
17
+ @registered_types = {}
18
+ @valid_options = %i[class parent ttl default db key redis]
19
+ @db = nil
20
+ @ttl = nil
21
+
22
+ class << self
23
+ attr_reader :registered_types, :valid_options
24
+ attr_accessor :parent
25
+ attr_writer :ttl, :db, :uri
26
+
27
+ # To be called inside every class that inherits RedisType
28
+ # +methname+ is the term used for the class and instance methods
29
+ # that are created for the given +klass+ (e.g. set, list, etc)
30
+ def register(klass, methname)
31
+ Familia.ld "[#{self}] Registering #{klass} as #{methname}"
32
+
33
+ @registered_types[methname] = klass
34
+ end
35
+
36
+ def ttl(val = nil)
37
+ @ttl = val unless val.nil?
38
+ @ttl || parent&.ttl
39
+ end
40
+
41
+ def db(val = nil)
42
+ @db = val unless val.nil?
43
+ @db || parent&.db
44
+ end
45
+
46
+ def uri(val = nil)
47
+ @uri = val unless val.nil?
48
+ @uri || (parent ? parent.uri : Familia.uri)
49
+ end
50
+
51
+ def inherited(obj)
52
+ obj.db = db
53
+ obj.ttl = ttl
54
+ obj.uri = uri
55
+ obj.parent = self
56
+ super(obj)
57
+ end
58
+
59
+ def valid_keys_only(opts)
60
+ opts.select { |k, _| RedisType.valid_options.include? k }
61
+ end
62
+ end
63
+
64
+ attr_reader :name, :parent, :opts
65
+ attr_writer :dump_method, :load_method
66
+
67
+ # +name+: If parent is set, this will be used as the suffix
68
+ # for rediskey. Otherwise this becomes the value of the key.
69
+ # If this is an Array, the elements will be joined.
70
+ #
71
+ # Options:
72
+ #
73
+ # :class => A class that responds to Familia.load_method and
74
+ # Familia.dump_method. These will be used when loading and
75
+ # saving data from/to redis to unmarshal/marshal the class.
76
+ #
77
+ # :parent => The Familia object that this redistype object belongs
78
+ # to. This can be a class that includes Familia or an instance.
79
+ #
80
+ # :ttl => the time to live in seconds. When not nil, this will
81
+ # set the redis expire for this key whenever #save is called.
82
+ # You can also call it explicitly via #update_expiration.
83
+ #
84
+ # :default => the default value (String-only)
85
+ #
86
+ # :db => the redis database to use (ignored if :redis is used).
87
+ #
88
+ # :redis => an instance of Redis.
89
+ #
90
+ # :key => a hardcoded key to use instead of the deriving the from
91
+ # the name and parent (e.g. a derived key: customer:custid:secret_counter).
92
+ #
93
+ # Uses the redis connection of the parent or the value of
94
+ # opts[:redis] or Familia.redis (in that order).
95
+ def initialize(name, opts = {})
96
+ #Familia.ld " [initializing] #{self.class} #{opts}"
97
+ @name = name
98
+ @name = @name.join(Familia.delim) if @name.is_a?(Array)
99
+
100
+ # Remove all keys from the opts that are not in the allowed list
101
+ @opts = opts || {}
102
+ @opts = RedisType.valid_keys_only(@opts)
103
+
104
+ init if respond_to? :init
105
+ end
106
+
107
+ def redis
108
+ return @redis if @redis
109
+
110
+ parent? ? parent.redis : Familia.redis(opts[:db])
111
+ end
112
+
113
+ # Produces the full redis key for this object.
114
+ def rediskey
115
+ Familia.ld "[rediskey] #{name} for #{self.class} (#{opts})"
116
+
117
+ # Return the hardcoded key if it's set. This is useful for
118
+ # support legacy keys that aren't derived in the same way.
119
+ return opts[:key] if opts[:key]
120
+
121
+ if parent_instance?
122
+ # This is an instance-level redistype object so the parent instance's
123
+ # rediskey method is defined in Familia::Horreum::InstanceMethods.
124
+ parent.rediskey(name)
125
+ elsif parent_class?
126
+ # This is a class-level redistype object so the parent class' rediskey
127
+ # method is defined in Familia::Horreum::ClassMethods.
128
+ parent.rediskey(name, nil)
129
+ else
130
+ # This is a standalone RedisType object where it's name
131
+ # is the full key.
132
+ name
133
+ end
134
+ end
135
+
136
+ def class?
137
+ !@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
138
+ end
139
+
140
+ def parent_instance?
141
+ parent.is_a?(Familia::Horreum)
142
+ end
143
+
144
+ def parent_class?
145
+ parent.is_a?(Class) && parent <= Familia::Horreum
146
+ end
147
+
148
+ def parent?
149
+ parent_class? || parent_instance?
150
+ end
151
+
152
+ def parent
153
+ @opts[:parent]
154
+ end
155
+
156
+ def ttl
157
+ @opts[:ttl] || self.class.ttl
158
+ end
159
+
160
+ def db
161
+ @opts[:db] || self.class.db
162
+ end
163
+
164
+ def uri
165
+ @opts[:uri] || self.class.uri
166
+ end
167
+
168
+ def dump_method
169
+ @dump_method || self.class.dump_method
170
+ end
171
+
172
+ def load_method
173
+ @load_method || self.class.load_method
174
+ end
175
+
176
+ include Commands
177
+ include Serialization
178
+ end
179
+
180
+ require_relative 'types/list'
181
+ require_relative 'types/unsorted_set'
182
+ require_relative 'types/sorted_set'
183
+ require_relative 'types/hashkey'
184
+ require_relative 'types/string'
185
+ end
@@ -0,0 +1,38 @@
1
+ # rubocop:disable all
2
+
3
+ module Familia
4
+
5
+ @delim = ':'
6
+ @prefix = nil
7
+ @suffix = :object
8
+ @ttl = nil
9
+ @db = nil
10
+
11
+ module Settings
12
+
13
+ attr_writer :delim, :suffix, :ttl, :db, :prefix
14
+
15
+ def delim(val = nil)
16
+ @delim = val if val
17
+ @delim
18
+ end
19
+
20
+ def prefix(val = nil)
21
+ @prefix = val if val
22
+ @prefix
23
+ end
24
+
25
+ def suffix(val = nil)
26
+ @suffix = val if val
27
+ @suffix
28
+ end
29
+
30
+ # We define this do-nothing method because it reads better
31
+ # than simply Familia.suffix in some contexts.
32
+ def default_suffix
33
+ suffix
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Familia
4
+ class HashKey < RedisType
5
+ def size
6
+ redis.hlen rediskey
7
+ end
8
+ alias length size
9
+
10
+ def empty?
11
+ size.zero?
12
+ end
13
+
14
+ # +return+ [Integer] Returns 1 if the field is new and added, 0 if the
15
+ # field already existed and the value was updated.
16
+ def []=(field, val)
17
+ ret = redis.hset rediskey, field, to_redis(val)
18
+ update_expiration
19
+ ret
20
+ rescue TypeError => e
21
+ Familia.le "[hset]= #{e.message}"
22
+ Familia.ld "[hset]= #{rediskey} #{field}=#{val}" if Familia.debug
23
+ echo :hset, caller(1..1).first if Familia.debug # logs via echo to redis and back
24
+ klass = val.class
25
+ msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{rediskey}"
26
+ raise e.class, msg
27
+ end
28
+ alias put []=
29
+ alias store []=
30
+
31
+ def [](field)
32
+ from_redis redis.hget(rediskey, field)
33
+ end
34
+ alias get []
35
+
36
+ def fetch(field, default = nil)
37
+ ret = self[field]
38
+ if ret.nil?
39
+ raise IndexError, "No such index for: #{field}" if default.nil?
40
+
41
+ default
42
+ else
43
+ ret
44
+ end
45
+ end
46
+
47
+ def keys
48
+ redis.hkeys rediskey
49
+ end
50
+
51
+ def values
52
+ el = redis.hvals(rediskey)
53
+ multi_from_redis(*el)
54
+ end
55
+
56
+ def all
57
+ # TODO: Use from_redis. Also name `all` is confusing with
58
+ # Onetime::Customer.all which returns all customers.
59
+ redis.hgetall rediskey
60
+ end
61
+ alias to_hash all
62
+ alias clone all
63
+
64
+ def has_key?(field)
65
+ redis.hexists rediskey, field
66
+ end
67
+ alias include? has_key?
68
+ alias member? has_key?
69
+
70
+ def delete(field)
71
+ redis.hdel rediskey, field
72
+ end
73
+ alias remove delete
74
+ alias rem delete
75
+ alias del delete
76
+
77
+ def increment(field, by = 1)
78
+ redis.hincrby(rediskey, field, by).to_i
79
+ end
80
+ alias incr increment
81
+ alias incrby increment
82
+
83
+ def decrement(field, by = 1)
84
+ increment field, -by
85
+ end
86
+ alias decr decrement
87
+ alias decrby decrement
88
+
89
+ def update(hsh = {})
90
+ raise ArgumentError, 'Argument to bulk_set must be a hash' unless hsh.is_a?(Hash)
91
+
92
+ data = hsh.inject([]) { |ret, pair| ret << [pair[0], to_redis(pair[1])] }.flatten
93
+
94
+ ret = redis.hmset(rediskey, *data)
95
+ update_expiration
96
+ ret
97
+ end
98
+ alias merge! update
99
+
100
+ def values_at *fields
101
+ el = redis.hmget(rediskey, *fields.flatten.compact)
102
+ multi_from_redis(*el)
103
+ end
104
+
105
+ Familia::RedisType.register self, :hash # legacy, deprecated
106
+ Familia::RedisType.register self, :hashkey
107
+ end
108
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Familia
4
+ class List < RedisType
5
+ def size
6
+ redis.llen rediskey
7
+ end
8
+ alias length size
9
+
10
+ def empty?
11
+ size.zero?
12
+ end
13
+
14
+ def push *values
15
+ echo :push, caller(1..1).first if Familia.debug
16
+ values.flatten.compact.each { |v| redis.rpush rediskey, to_redis(v) }
17
+ redis.ltrim rediskey, -@opts[:maxlength], -1 if @opts[:maxlength]
18
+ update_expiration
19
+ self
20
+ end
21
+
22
+ def <<(val)
23
+ push val
24
+ end
25
+ alias add <<
26
+
27
+ def unshift *values
28
+ values.flatten.compact.each { |v| redis.lpush rediskey, to_redis(v) }
29
+ # TODO: test maxlength
30
+ redis.ltrim rediskey, 0, @opts[:maxlength] - 1 if @opts[:maxlength]
31
+ update_expiration
32
+ self
33
+ end
34
+
35
+ def pop
36
+ from_redis redis.rpop(rediskey)
37
+ end
38
+
39
+ def shift
40
+ from_redis redis.lpop(rediskey)
41
+ end
42
+
43
+ def [](idx, count = nil)
44
+ if idx.is_a? Range
45
+ range idx.first, idx.last
46
+ elsif count
47
+ case count <=> 0
48
+ when 1 then range(idx, idx + count - 1)
49
+ when 0 then []
50
+ when -1 then nil
51
+ end
52
+ else
53
+ at idx
54
+ end
55
+ end
56
+ alias slice []
57
+
58
+ def delete(v, count = 0)
59
+ redis.lrem rediskey, count, to_redis(v)
60
+ end
61
+ alias remove delete
62
+ alias rem delete
63
+ alias del delete
64
+
65
+ def range(sidx = 0, eidx = -1)
66
+ el = rangeraw sidx, eidx
67
+ multi_from_redis(*el)
68
+ end
69
+
70
+ def rangeraw(sidx = 0, eidx = -1)
71
+ redis.lrange(rediskey, sidx, eidx)
72
+ end
73
+
74
+ def members(count = -1)
75
+ echo :members, caller(1..1).first if Familia.debug
76
+ count -= 1 if count.positive?
77
+ range 0, count
78
+ end
79
+ alias all members
80
+ alias to_a members
81
+
82
+ def membersraw(count = -1)
83
+ count -= 1 if count.positive?
84
+ rangeraw 0, count
85
+ end
86
+
87
+ def each(&blk)
88
+ range.each(&blk)
89
+ end
90
+
91
+ def each_with_index(&blk)
92
+ range.each_with_index(&blk)
93
+ end
94
+
95
+ def eachraw(&blk)
96
+ rangeraw.each(&blk)
97
+ end
98
+
99
+ def eachraw_with_index(&blk)
100
+ rangeraw.each_with_index(&blk)
101
+ end
102
+
103
+ def collect(&blk)
104
+ range.collect(&blk)
105
+ end
106
+
107
+ def select(&blk)
108
+ range.select(&blk)
109
+ end
110
+
111
+ def collectraw(&blk)
112
+ rangeraw.collect(&blk)
113
+ end
114
+
115
+ def selectraw(&blk)
116
+ rangeraw.select(&blk)
117
+ end
118
+
119
+ def at(idx)
120
+ from_redis redis.lindex(rediskey, idx)
121
+ end
122
+
123
+ def first
124
+ at 0
125
+ end
126
+
127
+ def last
128
+ at(-1)
129
+ end
130
+
131
+ # TODO: def replace
132
+ ## Make the value stored at KEY identical to the given list
133
+ # define_method :"#{name}_sync" do |*latest|
134
+ # latest = latest.flatten.compact
135
+ # # Do nothing if we're given an empty Array.
136
+ # # Otherwise this would clear all current values
137
+ # if latest.empty?
138
+ # false
139
+ # else
140
+ # # Convert to a list of index values if we got the actual objects
141
+ # latest = latest.collect { |obj| obj.index } if klass === latest.first
142
+ # current = send("#{name_plural}raw")
143
+ # added = latest-current
144
+ # removed = current-latest
145
+ # #Familia.info "#{self.index}: adding: #{added}"
146
+ # added.each { |v| self.send("add_#{name_singular}", v) }
147
+ # #Familia.info "#{self.index}: removing: #{removed}"
148
+ # removed.each { |v| self.send("remove_#{name_singular}", v) }
149
+ # true
150
+ # end
151
+ # end
152
+
153
+ Familia::RedisType.register self, :list
154
+ end
155
+ end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Familia
4
+ class SortedSet < RedisType
5
+ def size
6
+ redis.zcard rediskey
7
+ end
8
+ alias length size
9
+
10
+ def empty?
11
+ size.zero?
12
+ end
13
+
14
+ # Adds a new element to the sorted set with the current timestamp as the
15
+ # score.
16
+ #
17
+ # This method provides a convenient way to add elements to the sorted set
18
+ # without explicitly specifying a score. It uses the current Unix timestamp
19
+ # as the score, which effectively sorts elements by their insertion time.
20
+ #
21
+ # @param val [Object] The value to be added to the sorted set.
22
+ # @return [Integer] Returns 1 if the element is new and added, 0 if the
23
+ # element already existed and the score was updated.
24
+ #
25
+ # @example
26
+ # sorted_set << "new_element"
27
+ #
28
+ # @note This is a non-standard operation for sorted sets as it doesn't allow
29
+ # specifying a custom score. Use `add` or `[]=` for more control.
30
+ #
31
+ def <<(val)
32
+ add(Time.now.to_i, val)
33
+ end
34
+
35
+ # NOTE: The argument order is the reverse of #add. We do this to
36
+ # more naturally align with how the [] and []= methods are used.
37
+ #
38
+ # e.g.
39
+ # obj.metrics[VALUE] = SCORE
40
+ # obj.metrics[VALUE] # => SCORE
41
+ #
42
+ def []=(val, score)
43
+ add score, val
44
+ end
45
+
46
+ def add(score, val)
47
+ ret = redis.zadd rediskey, score, to_redis(val)
48
+ update_expiration
49
+ ret
50
+ end
51
+
52
+ def score(val)
53
+ ret = redis.zscore rediskey, to_redis(val, false)
54
+ ret&.to_f
55
+ end
56
+ alias [] score
57
+
58
+ def member?(val)
59
+ Familia.trace :MEMBER, redis, "#{val}<#{val.class}>", caller(1..1) if Familia.debug?
60
+ !rank(val).nil?
61
+ end
62
+ alias include? member?
63
+
64
+ # rank of member +v+ when ordered lowest to highest (starts at 0)
65
+ def rank(v)
66
+ ret = redis.zrank rediskey, to_redis(v, false)
67
+ ret&.to_i
68
+ end
69
+
70
+ # rank of member +v+ when ordered highest to lowest (starts at 0)
71
+ def revrank(v)
72
+ ret = redis.zrevrank rediskey, to_redis(v, false)
73
+ ret&.to_i
74
+ end
75
+
76
+ def members(count = -1, opts = {})
77
+ count -= 1 if count.positive?
78
+ el = membersraw count, opts
79
+ multi_from_redis(*el)
80
+ end
81
+ alias to_a members
82
+ alias all members
83
+
84
+ def membersraw(count = -1, opts = {})
85
+ count -= 1 if count.positive?
86
+ rangeraw 0, count, opts
87
+ end
88
+
89
+ def revmembers(count = -1, opts = {})
90
+ count -= 1 if count.positive?
91
+ el = revmembersraw count, opts
92
+ multi_from_redis(*el)
93
+ end
94
+
95
+ def revmembersraw(count = -1, opts = {})
96
+ count -= 1 if count.positive?
97
+ revrangeraw 0, count, opts
98
+ end
99
+
100
+ def each(&blk)
101
+ members.each(&blk)
102
+ end
103
+
104
+ def each_with_index(&blk)
105
+ members.each_with_index(&blk)
106
+ end
107
+
108
+ def collect(&blk)
109
+ members.collect(&blk)
110
+ end
111
+
112
+ def select(&blk)
113
+ members.select(&blk)
114
+ end
115
+
116
+ def eachraw(&blk)
117
+ membersraw.each(&blk)
118
+ end
119
+
120
+ def eachraw_with_index(&blk)
121
+ membersraw.each_with_index(&blk)
122
+ end
123
+
124
+ def collectraw(&blk)
125
+ membersraw.collect(&blk)
126
+ end
127
+
128
+ def selectraw(&blk)
129
+ membersraw.select(&blk)
130
+ end
131
+
132
+ def range(sidx, eidx, opts = {})
133
+ echo :range, caller(1..1).first if Familia.debug
134
+ el = rangeraw(sidx, eidx, opts)
135
+ multi_from_redis(*el)
136
+ end
137
+
138
+ def rangeraw(sidx, eidx, opts = {})
139
+ # NOTE: :withscores (no underscore) is the correct naming for the
140
+ # redis-4.x gem. We pass :withscores through explicitly b/c
141
+ # redis.zrange et al only accept that one optional argument.
142
+ # Passing `opts`` through leads to an ArgumentError:
143
+ #
144
+ # sorted_sets.rb:374:in `zrevrange': wrong number of arguments (given 4, expected 3) (ArgumentError)
145
+ #
146
+ redis.zrange(rediskey, sidx, eidx, **opts)
147
+ end
148
+
149
+ def revrange(sidx, eidx, opts = {})
150
+ echo :revrange, caller(1..1).first if Familia.debug
151
+ el = revrangeraw(sidx, eidx, opts)
152
+ multi_from_redis(*el)
153
+ end
154
+
155
+ def revrangeraw(sidx, eidx, opts = {})
156
+ redis.zrevrange(rediskey, sidx, eidx, **opts)
157
+ end
158
+
159
+ # e.g. obj.metrics.rangebyscore (now-12.hours), now, :limit => [0, 10]
160
+ def rangebyscore(sscore, escore, opts = {})
161
+ echo :rangebyscore, caller(1..1).first if Familia.debug
162
+ el = rangebyscoreraw(sscore, escore, opts)
163
+ multi_from_redis(*el)
164
+ end
165
+
166
+ def rangebyscoreraw(sscore, escore, opts = {})
167
+ echo :rangebyscoreraw, caller(1..1).first if Familia.debug
168
+ redis.zrangebyscore(rediskey, sscore, escore, **opts)
169
+ end
170
+
171
+ # e.g. obj.metrics.revrangebyscore (now-12.hours), now, :limit => [0, 10]
172
+ def revrangebyscore(sscore, escore, opts = {})
173
+ echo :revrangebyscore, caller(1..1).first if Familia.debug
174
+ el = revrangebyscoreraw(sscore, escore, opts)
175
+ multi_from_redis(*el)
176
+ end
177
+
178
+ def revrangebyscoreraw(sscore, escore, opts = {})
179
+ echo :revrangebyscoreraw, caller(1..1).first if Familia.debug
180
+ opts[:with_scores] = true if opts[:withscores]
181
+ redis.zrevrangebyscore(rediskey, sscore, escore, opts)
182
+ end
183
+
184
+ def remrangebyrank(srank, erank)
185
+ redis.zremrangebyrank rediskey, srank, erank
186
+ end
187
+
188
+ def remrangebyscore(sscore, escore)
189
+ redis.zremrangebyscore rediskey, sscore, escore
190
+ end
191
+
192
+ def increment(val, by = 1)
193
+ redis.zincrby(rediskey, by, val).to_i
194
+ end
195
+ alias incr increment
196
+ alias incrby increment
197
+
198
+ def decrement(val, by = 1)
199
+ increment val, -by
200
+ end
201
+ alias decr decrement
202
+ alias decrby decrement
203
+
204
+ def delete(val)
205
+ Familia.trace :DELETE, redis, "#{val}<#{val.class}>", caller(1..1) if Familia.debug?
206
+ # We use `strict_values: false` here to allow for the deletion of values
207
+ # that are in the sorted set. If it's a horreum object, the value is
208
+ # the identifier and not a serialized version of the object. So either
209
+ # the value exists in the sorted set or it doesn't -- we don't need to
210
+ # raise an error if it's not found.
211
+ redis.zrem rediskey, to_redis(val, false)
212
+ end
213
+ alias remove delete
214
+ alias rem delete
215
+ alias del delete
216
+
217
+ def at(idx)
218
+ range(idx, idx).first
219
+ end
220
+
221
+ # Return the first element in the list. Redis: ZRANGE(0)
222
+ def first
223
+ at(0)
224
+ end
225
+
226
+ # Return the last element in the list. Redis: ZRANGE(-1)
227
+ def last
228
+ at(-1)
229
+ end
230
+
231
+ Familia::RedisType.register self, :sorted_set
232
+ Familia::RedisType.register self, :zset
233
+ end
234
+ end