redstruct 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -11
  3. data/Rakefile +5 -5
  4. data/lib/redstruct/all.rb +14 -0
  5. data/lib/redstruct/configuration.rb +9 -6
  6. data/lib/redstruct/connection_proxy.rb +123 -0
  7. data/lib/redstruct/counter.rb +96 -0
  8. data/lib/redstruct/error.rb +2 -0
  9. data/lib/redstruct/factory/object.rb +31 -0
  10. data/lib/redstruct/factory.rb +94 -55
  11. data/lib/redstruct/hash.rb +123 -0
  12. data/lib/redstruct/list.rb +315 -0
  13. data/lib/redstruct/lock.rb +183 -0
  14. data/lib/redstruct/script.rb +104 -0
  15. data/lib/redstruct/set.rb +155 -0
  16. data/lib/redstruct/sorted_set/slice.rb +124 -0
  17. data/lib/redstruct/sorted_set.rb +153 -0
  18. data/lib/redstruct/string.rb +66 -0
  19. data/lib/redstruct/struct.rb +87 -0
  20. data/lib/redstruct/utils/coercion.rb +14 -8
  21. data/lib/redstruct/utils/inspectable.rb +8 -4
  22. data/lib/redstruct/utils/iterable.rb +52 -0
  23. data/lib/redstruct/utils/scriptable.rb +32 -6
  24. data/lib/redstruct/version.rb +4 -1
  25. data/lib/redstruct.rb +17 -51
  26. data/lib/yard/defscript_handler.rb +5 -3
  27. data/test/redstruct/configuration_test.rb +13 -0
  28. data/test/redstruct/connection_proxy_test.rb +85 -0
  29. data/test/redstruct/counter_test.rb +108 -0
  30. data/test/redstruct/factory/object_test.rb +21 -0
  31. data/test/redstruct/factory_test.rb +136 -0
  32. data/test/redstruct/hash_test.rb +138 -0
  33. data/test/redstruct/list_test.rb +244 -0
  34. data/test/redstruct/lock_test.rb +108 -0
  35. data/test/redstruct/script_test.rb +53 -0
  36. data/test/redstruct/set_test.rb +219 -0
  37. data/test/redstruct/sorted_set/slice_test.rb +10 -0
  38. data/test/redstruct/sorted_set_test.rb +219 -0
  39. data/test/redstruct/string_test.rb +8 -0
  40. data/test/redstruct/struct_test.rb +61 -0
  41. data/test/redstruct/utils/coercion_test.rb +33 -0
  42. data/test/redstruct/utils/inspectable_test.rb +31 -0
  43. data/test/redstruct/utils/iterable_test.rb +94 -0
  44. data/test/redstruct/utils/scriptable_test.rb +67 -0
  45. data/test/redstruct_test.rb +14 -0
  46. data/test/test_helper.rb +77 -1
  47. metadata +58 -26
  48. data/lib/redstruct/connection.rb +0 -47
  49. data/lib/redstruct/factory/creation.rb +0 -95
  50. data/lib/redstruct/factory/deserialization.rb +0 -7
  51. data/lib/redstruct/hls/lock.rb +0 -175
  52. data/lib/redstruct/hls/queue.rb +0 -29
  53. data/lib/redstruct/hls.rb +0 -2
  54. data/lib/redstruct/types/base.rb +0 -36
  55. data/lib/redstruct/types/counter.rb +0 -65
  56. data/lib/redstruct/types/hash.rb +0 -72
  57. data/lib/redstruct/types/list.rb +0 -76
  58. data/lib/redstruct/types/script.rb +0 -56
  59. data/lib/redstruct/types/set.rb +0 -96
  60. data/lib/redstruct/types/sorted_set.rb +0 -129
  61. data/lib/redstruct/types/string.rb +0 -64
  62. data/lib/redstruct/types/struct.rb +0 -58
  63. data/lib/releaser/logger.rb +0 -15
  64. data/lib/releaser/repository.rb +0 -32
  65. data/lib/tasks/release.rake +0 -49
  66. data/test/redstruct/restruct_test.rb +0 -4
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'redstruct/struct'
5
+ require 'redstruct/utils/iterable'
6
+
7
+ module Redstruct
8
+ # Mapping between Redis and Ruby sets. There is no caching mechanism in play, so most methods actually do access
9
+ # the underlying redis connection. Also, keep in mind Redis converts all values strings on the DB side
10
+ class Set < Redstruct::Struct
11
+ include Redstruct::Utils::Iterable
12
+
13
+ # Clears the set by simply removing the key from the DB
14
+ # @see Redstruct::Struct#delete
15
+ def clear
16
+ delete
17
+ end
18
+
19
+ # Returns random items from the set
20
+ # @param [Integer] count the number of items to return
21
+ # @return [String, Set] if count is one, then return the item; otherwise returns a set
22
+ def random(count: 1)
23
+ list = self.connection.srandmember(@key, count)
24
+
25
+ return nil if list.nil?
26
+ return count == 1 ? list[0] : ::Set.new(list)
27
+ end
28
+
29
+ # Checks if the set is empty by checking if the key actually exists on the underlying redis db
30
+ # @see Redstruct::Struct#exists?
31
+ # @return [Boolean] true if it is empty, false otherwise
32
+ def empty?
33
+ return !exists?
34
+ end
35
+
36
+ # Checks if the set contains this particular item
37
+ # @param [#to_s] item the item to check for
38
+ # @return [Boolean] true if the set contains the item, false otherwise
39
+ def contain?(item)
40
+ return coerce_bool(self.connection.sismember(@key, item))
41
+ end
42
+ alias include? contain?
43
+
44
+ # Adds the given items to the set
45
+ # @param [Array<#to_s>] items the items to add to the set
46
+ # @return [Boolean, Integer] when only one item, returns true or false on insertion, otherwise the number of items added
47
+ def add(*items)
48
+ return self.connection.sadd(@key, items)
49
+ end
50
+ alias << add
51
+
52
+ # Pops and returns an item from the set.
53
+ # NOTE: Since this is a redis set, keep in mind that popping the last element of the set effectively deletes the set
54
+ # @return [String] popped item
55
+ def pop
56
+ return self.connection.spop(@key)
57
+ end
58
+
59
+ # Removes the given items from the set.
60
+ # @param [Array<#to_s>] items the items to remove from the set
61
+ # @return [Boolean, Integer] when only one item, returns true or false on deletion, otherwise the number of items removed
62
+ def remove(*items)
63
+ return self.connection.srem(@key, items)
64
+ end
65
+
66
+ # @return [Integer] the number of items in the set
67
+ def size
68
+ return self.connection.scard(@key).to_i
69
+ end
70
+
71
+ # Computes the difference of the two sets and stores the result in `dest`. If no destination provided, computes
72
+ # the results in memory.
73
+ # @param [Redstruct::Set] other set the set to subtract
74
+ # @param [Redstruct::Set, String] dest if nil, results are computed in memory. if a string, a new Redstruct::Set is
75
+ # constructed with the string as the key, and results are stored there. if already a Redstruct::Set, results are stored there.
76
+ # @return [::Set, Integer] if dest was provided, returns the number of elements in the destination; otherwise a standard Ruby set containing the difference
77
+ def difference(other, dest: nil)
78
+ destination = coerce_destination(dest)
79
+ results = if destination.nil?
80
+ ::Set.new(self.connection.sdiff(@key, other.key))
81
+ else
82
+ self.connection.sdiffstore(destination.key, @key, other.key)
83
+ end
84
+
85
+ return results
86
+ end
87
+ alias - difference
88
+
89
+ # Computes the interesection of the two sets and stores the result in `dest`. If no destination provided, computes
90
+ # the results in memory.
91
+ # @param [Redstruct::Set] other set the set to intersect
92
+ # @param [Redstruct::Set, String] dest if nil, results are computed in memory. if a string, a new Redstruct::Set is
93
+ # constructed with the string as the key, and results are stored there. if already a Redstruct::Set, results are stored there.
94
+ # @return [::Set, Integer] if dest was provided, returns the number of elements in the destination; otherwise a standard Ruby set containing the intersection
95
+ def intersection(other, dest: nil)
96
+ destination = coerce_destination(dest)
97
+ results = if destination.nil?
98
+ ::Set.new(self.connection.sinter(@key, other.key))
99
+ else
100
+ self.connection.sinterstore(destination.key, @key, other.key)
101
+ end
102
+
103
+ return results
104
+ end
105
+ alias | intersection
106
+
107
+ # Computes the union of the two sets and stores the result in `dest`. If no destination provided, computes
108
+ # the results in memory.
109
+ # @param [Redstruct::Set] other set the set to add
110
+ # @param [Redstruct::Set, String] dest if nil, results are computed in memory. if a string, a new Redstruct::Set is
111
+ # constructed with the string as the key, and results are stored there. if already a Redstruct::Set, results are stored there.
112
+ # @return [::Set, Integer] if dest was provided, returns the number of elements in the destination; otherwise a standard Ruby set containing the union
113
+ def union(other, dest: nil)
114
+ destination = coerce_destination(dest)
115
+ results = if destination.nil?
116
+ ::Set.new(self.connection.sunion(@key, other.key))
117
+ else
118
+ self.connection.sunionstore(destination.key, @key, other.key)
119
+ end
120
+
121
+ return results
122
+ end
123
+ alias + union
124
+
125
+ # Use redis-rb sscan_each method to iterate over particular keys
126
+ # @return [Enumerator] base enumerator to iterate of the namespaced keys
127
+ def to_enum(match: '*', count: 10)
128
+ return self.connection.sscan_each(@key, match: match, count: count)
129
+ end
130
+
131
+ # Returns an array representation of the set. Ordering is random and defined by redis
132
+ # NOTE: If the set is particularly large, consider using #each
133
+ # @return [Array<String>] an array of all items contained in the set
134
+ def to_a
135
+ return self.connection.smembers(@key)
136
+ end
137
+
138
+ # Loads all members of the set and converts them to a Ruby set.
139
+ # NOTE: If the set is particularly large, consider using #each
140
+ # @return [::Set] ruby set of all items stored on redis for this set
141
+ def to_set
142
+ return ::Set.new(to_a)
143
+ end
144
+
145
+ def coerce_destination(dest)
146
+ case dest
147
+ when ::String
148
+ @factory.set(dest)
149
+ when self.class
150
+ dest
151
+ end
152
+ end
153
+ private :coerce_destination
154
+ end
155
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redstruct
4
+ class SortedSet
5
+ # Utility class to allow operations on portions of the set only
6
+ # TODO: Support #length property (using LIMIT offset count) of the different
7
+ # range commands, so a slice could be defined as starting at offset X and
8
+ # having length Y, instead of just starting at X and finishing at Y.
9
+ class Slice < Redstruct::Factory::Object
10
+ # @return [String] the key for the underlying sorted set
11
+ attr_reader :key
12
+
13
+ # @return [String, Float] the lower bound of the slice
14
+ attr_reader :lower
15
+
16
+ # @return [String, Float] the upper bound of the slice
17
+ attr_reader :upper
18
+
19
+ # @return [Boolean] if true, then assumes the slice is lexicographically sorted
20
+ attr_reader :lex
21
+
22
+ # @return [Boolean] if true, assumes the range bounds are exclusive
23
+ attr_reader :exclusive
24
+
25
+ # @param [String, Float] lower lower bound for the slice operation
26
+ # @param [String, Float] upper upper bound for the slice operation
27
+ # @param [Boolean] lex if true, uses lexicographic operations
28
+ # @param [Boolean] exclusive if true, assumes bounds are exclusive
29
+ def initialize(set, lower: nil, upper: nil, lex: false, exclusive: false)
30
+ super(factory: set.factory)
31
+
32
+ @key = set.key
33
+ @lex = lex
34
+ @exclusive = exclusive
35
+
36
+ lower ||= -Float::INFINITY
37
+ upper ||= Float::INFINITY
38
+
39
+ if @lex
40
+ @lower = parse_lex_bound(lower)
41
+ @upper = parse_lex_bound(upper)
42
+ else
43
+ @lower = parse_bound(lower)
44
+ @upper = parse_bound(upper)
45
+ end
46
+ end
47
+
48
+ # @return [Array<String>] returns an array of values for the given bounds
49
+ def to_a
50
+ if @lex
51
+ self.connection.zrangebylex(@key, @lower, @upper)
52
+ else
53
+ self.connection.zrangebyscore(@key, @lower, @upper)
54
+ end
55
+ end
56
+
57
+ # @return [Array<String>] returns an array of values reversed
58
+ def reverse
59
+ if @lex
60
+ self.connection.zrevrangebylex(@key, @lower, @upper)
61
+ else
62
+ self.connection.zrevrangebyscore(@key, @lower, @upper)
63
+ end
64
+ end
65
+
66
+ # @return [Integer] the number of elements removed
67
+ def remove
68
+ if @lex
69
+ self.connection.zremrangebylex(@key, @lower, @upper)
70
+ else
71
+ self.connection.zremrangebyscore(@key, @lower, @upper)
72
+ end
73
+ end
74
+
75
+ # @return [Integer] number of elements in the slice
76
+ def size
77
+ if @lex
78
+ self.connection.zlexcount(@key, @lower, @upper)
79
+ else
80
+ self.connection.zcount(@key, @lower, @upper)
81
+ end
82
+ end
83
+
84
+ # TODO: consider using SortedSet, some other data structure, or nothing
85
+ # @return [::Set] an unordered set representation of the slice
86
+ def to_set
87
+ ::Set.new(to_a)
88
+ end
89
+
90
+ def inspectable_attributes
91
+ { lower: @lower, upper: @upper, lex: @lex, exclusive: @exclusive, key: @key }
92
+ end
93
+
94
+ private
95
+
96
+ # ( is exclusive, [ is inclusive
97
+ def parse_lex_bound(bound)
98
+ case bound
99
+ when -Float::INFINITY then '-'
100
+ when Float::INFINITY then '+'
101
+ else prefix(bound, inclusion: '[', exclusion: '(')
102
+ end
103
+ end
104
+
105
+ # ( is exclusive
106
+ def parse_bound(bound)
107
+ case bound
108
+ when -Float::INFINITY then '-inf'
109
+ when Float::INFINITY then '+inf'
110
+ when String then prefix(bound, exclusion: '(')
111
+ else prefix(bound.to_f, exclusion: '(')
112
+ end
113
+ end
114
+
115
+ def prefix(value, inclusion: '', exclusion: '')
116
+ prefix = @exclusive ? exclusion : inclusion
117
+ prefixed = value
118
+ prefixed = "#{prefix}#{value}" unless prefix.empty? || prefixed.to_s.start_with?(prefix)
119
+
120
+ return prefixed
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'redstruct/struct'
5
+ require 'redstruct/utils/iterable'
6
+
7
+ module Redstruct
8
+ # Mapping between Redis and Ruby sorted sets (with scores). There is no caching mechanism in play, so most methods actually do access
9
+ # the underlying redis connection. Also, keep in mind Redis converts all values strings on the DB side
10
+ class SortedSet < Redstruct::Struct
11
+ include Redstruct::Utils::Iterable
12
+
13
+ # @param [Boolean] lex if true, assumes the set is lexicographically sorted
14
+ def initialize(lex: false, **options)
15
+ super(**options)
16
+ @lex = lex
17
+ end
18
+
19
+ # @return [Boolean] true if this is a lexicographically sorted set
20
+ def lexicographic?
21
+ return @lex
22
+ end
23
+
24
+ # @param [Array<#to_s>] values the object to add to the set
25
+ # @param [Boolean] exists if true, only update elements that exist (do not add new ones)
26
+ # @param [Boolean] overwrite if false, do not update existing elements
27
+ # @return [Integer] the number of elements that have changed (includes new ones)
28
+ def add(*values, exists: false, overwrite: true)
29
+ options = { xx: exists, nx: !overwrite, ch: true }
30
+
31
+ if @lex
32
+ values = values.map do |pair|
33
+ member = pair.is_a?(Array) ? pair.last : pair
34
+ [0.0, member]
35
+ end
36
+ end
37
+
38
+ return self.connection.zadd(@key, values, options)
39
+ end
40
+
41
+ # @param [#to_s] member the member of the set whose score to increment
42
+ # @param [#to_f] by the amount to increment the score by
43
+ # @return [Float] the new score of the member
44
+ def increment(member, by: 1.0)
45
+ raise NotImplementedError, 'cannot increment the score of items in a lexicographically ordered set' if @lex
46
+ return self.connection.zincrby(@key, by.to_f, member.to_s).to_f
47
+ end
48
+
49
+ # @param [#to_s] member the member of the set whose score to decrement
50
+ # @param [#to_f] by the amount to decrement the score by
51
+ # @return [Float] the new score of the member
52
+ def decrement(member, by: 1.0)
53
+ return increment(member, by: -by.to_f)
54
+ end
55
+
56
+ # Removes all items from the set. Does this by simply deleting the key
57
+ # @see Redstruct::Struct#delete
58
+ def clear
59
+ delete
60
+ end
61
+
62
+ # Returns the number of items in the set. If you want to specify within a
63
+ # range, first get the slice and query its size.
64
+ # @return [Integer] the number of items in the set
65
+ def size
66
+ return self.connection.zcard(@key)
67
+ end
68
+
69
+ # Returns a slice or partial selection of the set.
70
+ # @see Redstruct::SortedSet::Slice#initialize
71
+ # @return [Redstruct::SortedSet::Slice] a newly created slice for this set
72
+ def slice(**options)
73
+ defaults = {
74
+ lower: nil,
75
+ upper: nil,
76
+ exclusive: false,
77
+ lex: @lex
78
+ }
79
+
80
+ self.class::Slice.new(self, **defaults.merge(options))
81
+ end
82
+
83
+ # Checks if the set contains any items.
84
+ # @return [Boolean] true if the key exists (meaning it contains at least 1 item), false otherwise
85
+ def empty?
86
+ return !exists?
87
+ end
88
+
89
+ # Relies on the score method, since it is O(1), whereas the index method is
90
+ # O(logn)
91
+ # @param [#to_s] item the item to check for
92
+ # @return [Boolean] true if the item is in the set, false otherwise
93
+ def contain?(item)
94
+ return coerce_bool(score(item))
95
+ end
96
+ alias include? contain?
97
+
98
+ # Returns the index of the item in the set, sorted ascending by score
99
+ # @param [#to_s] item the item to check for
100
+ # @return [Integer, nil] the index of the item, or nil if not found
101
+ def index(item)
102
+ return self.connection.zrank(@key, item)
103
+ end
104
+
105
+ # Returns the index of the item in the set, sorted descending by score
106
+ # @param [#to_s] item the item to check for
107
+ # @return [Integer, nil] the index of the item, or nil if not found
108
+ def rindex(item)
109
+ return self.connection.zrevrank(@key, item)
110
+ end
111
+
112
+ # Returns the score of the given item.
113
+ # @param [#to_s] item the item to check for
114
+ # @return [Float, nil] the score of the item, or nil if not found
115
+ def score(item)
116
+ return self.connection.zscore(@key, item)
117
+ end
118
+
119
+ # Removes the items from the set.
120
+ # @param [Array<#to_s>] items the items to remove from the set
121
+ # @return [Integer] the amount of items removed from the set
122
+ def remove(*items)
123
+ return self.connection.zrem(@key, items)
124
+ end
125
+
126
+ # Returns an array representation of the set, sorted by score ascending
127
+ # NOTE: It pulls the whole set into memory, so use each if that's a concern,
128
+ # or use slices with pre-determined ranges.
129
+ # @return [Array<Redstruct::Utils::ScoredValue>] all the items in the set, sorted by score ascending
130
+ def to_a
131
+ return slice.to_a
132
+ end
133
+
134
+ # TODO: Consider using ::SortedSet or some other data structure
135
+ # @return [::Set] an unordered set representation
136
+ def to_set
137
+ return slice.to_set
138
+ end
139
+
140
+ # Use redis-rb zscan_each method to iterate over particular keys
141
+ # @return [Enumerator] base enumerator to iterate of the namespaced keys
142
+ def to_enum(match: '*', count: 10, with_scores: false)
143
+ enumerator = self.connection.zscan_each(@key, match: match, count: count)
144
+ return enumerator if with_scores
145
+ return Enumerator.new do |yielder|
146
+ loop do
147
+ item, = enumerator.next
148
+ yielder << item
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redstruct/struct'
4
+ require 'redstruct/utils/scriptable'
5
+
6
+ module Redstruct
7
+ # Manipulation of redis strings
8
+ class String < Redstruct::Struct
9
+ include Redstruct::Utils::Scriptable
10
+
11
+ # @return [String, nil] the string value stored in the database
12
+ def get
13
+ return self.connection.get(@key)
14
+ end
15
+
16
+ # @param [#to_s] value the object to store
17
+ # @param [Integer] expiry the expiry time in seconds; if nil, will never expire
18
+ # @param [Boolean] nx Not Exists: if true, will not set the key if it already existed
19
+ # @param [Boolean] xx Already Exists: if true, will set the key only if it already existed
20
+ # @return [Boolean] true if set, false otherwise
21
+ def set(value, expiry: nil, nx: false, xx: false)
22
+ options = { nx: nx, xx: xx }
23
+ options[:ex] = expiry.to_i unless expiry.nil?
24
+
25
+ coerce_bool(self.connection.set(@key, value, options))
26
+ end
27
+
28
+ # @param [String] value The value to compare with
29
+ # @return [Boolean] True if deleted, false otherwise
30
+ def delete_if_equals(value)
31
+ coerce_bool(delete_if_equals_script(keys: @key, argv: value))
32
+ end
33
+
34
+ # @param [#to_s] value The object to store
35
+ # @return [String] The old value before setting it
36
+ def getset(value)
37
+ self.connection.getset(@key, value)
38
+ end
39
+
40
+ # @return [Integer] The length of the string
41
+ def length
42
+ self.connection.strlen(@key)
43
+ end
44
+
45
+ # @param [Integer] start Starting index of the slice
46
+ # @param [Integer] length Length of the slice; negative numbers start counting from the right (-1 = end)
47
+ # @return [Array<String>] The requested slice from <start> with length <length>
48
+ def slice(start = 0, length = -1)
49
+ length = start + length if length >= 0
50
+ return self.connection.getrange(@key, start, length)
51
+ end
52
+
53
+ # Deletes the key (keys[1]) iff the value is equal to argv[1].
54
+ # @param [Array<(::String)>] keys The key to delete
55
+ # @param [Array<(::String)>] argv The value to compare with
56
+ # @return [Integer] 1 if deleted, 0 otherwise
57
+ defscript :delete_if_equals_script, <<~LUA
58
+ local deleted = false
59
+ if redis.call("get", KEYS[1]) == ARGV[1] then
60
+ deleted = redis.call("del", KEYS[1])
61
+ end
62
+
63
+ return deleted
64
+ LUA
65
+ end
66
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redstruct/factory/object'
4
+ require 'redstruct/utils/coercion'
5
+
6
+ module Redstruct
7
+ # Base class for all redis structures which have a particular value for a given key
8
+ class Struct < Redstruct::Factory::Object
9
+ include Redstruct::Utils::Coercion
10
+
11
+ # @return [String] the key used to identify the struct on redis
12
+ attr_reader :key
13
+
14
+ # @param [String] key the key used to identify the struct on redis, already namespaced
15
+ def initialize(key:, **options)
16
+ super(**options)
17
+ @key = key
18
+ end
19
+
20
+ # @return [Boolean] Returns true if it exists in redis, false otherwise
21
+ def exists?
22
+ return self.connection.exists(@key)
23
+ end
24
+
25
+ # @return [Boolean] false if nothing was deleted in the DB, true if it was
26
+ def delete
27
+ return coerce_bool(self.connection.del(@key))
28
+ end
29
+
30
+ # Sets the key to expire after ttl seconds
31
+ # @param [#to_f] ttl the time to live in seconds (where 0.001 = 1ms)
32
+ # @return [Boolean] true if expired, false otherwise
33
+ def expire(ttl)
34
+ ttl = (ttl.to_f * 1000).floor
35
+ return coerce_bool(self.connection.pexpire(@key, ttl))
36
+ end
37
+
38
+ # Sets the key to expire at the given timestamp.
39
+ # @param [#to_f] time time or unix timestamp at which the key should expire; once converted to float, assumes 1.0 is one second, 0.001 is 1 ms
40
+ # @return [Boolean] true if expired, false otherwise
41
+ def expire_at(time)
42
+ time = (time.to_f * 1000).floor
43
+ return coerce_bool(self.connection.pexpireat(@key, time))
44
+ end
45
+
46
+ # Removes the expiry time from a key
47
+ # @return [Boolean] true if persisted, false otherwise
48
+ def persist
49
+ coerce_bool(self.connection.persist(@key))
50
+ end
51
+
52
+ # @return [String] the underlying redis type
53
+ def type
54
+ self.connection.type(@key)
55
+ end
56
+
57
+ # Returns the time to live of the key
58
+ # @return [Float] time to live in seconds as a float where 0.001 == 1 ms
59
+ def ttl
60
+ return self.connection.pttl(@key) / 1000.0
61
+ end
62
+
63
+ # Returns a serialized representation of the key, which can be used to store a value externally, and restored to
64
+ # redis using #restore
65
+ # NOTE: This does not capture the TTL of the struct. If there arises a need for this, we can always modify it,
66
+ # but for now this is a pure proxy of the redis dump command
67
+ # @return [String, nil] nil if the struct does not exist, otherwise serialized representation
68
+ def dump
69
+ return self.connection.dump(@key)
70
+ end
71
+
72
+ # Restores the struct to its serialized value as given
73
+ # @param [String] serialized serialized representation of the value
74
+ # @param [#to_f] ttl the time to live for the struct; defaults to 0 (meaning no expiry). 0.001 == 1ms
75
+ # @raise [Redis::CommandError] raised if the serialized value is incompatible or the key already exists
76
+ # @return [Boolean] true if restored, false otherwise
77
+ def restore(serialized, ttl: 0)
78
+ ttl = (ttl.to_f * 1000).floor
79
+ return self.connection.restore(@key, ttl, serialized)
80
+ end
81
+
82
+ # # @!visibility private
83
+ def inspectable_attributes
84
+ super.merge(key: @key)
85
+ end
86
+ end
87
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Redstruct
2
4
  module Utils
3
5
  # Coercion utilities to map Redis replies to Ruby types, or vice-versa
@@ -9,10 +11,12 @@ module Redstruct
9
11
  # @param [Object] value The value to coerce
10
12
  # @return [Array] The coerced value
11
13
  def coerce_array(value)
12
- return [] if value.nil?
13
- return value if value.is_a?(Array)
14
- return value.to_a if value.respond_to?(:to_a)
15
- return [value]
14
+ case value
15
+ when nil then []
16
+ when Array then value
17
+ else
18
+ value.respond_to?(:to_a) ? value.to_a : [value]
19
+ end
16
20
  end
17
21
  module_function :coerce_array
18
22
 
@@ -22,10 +26,12 @@ module Redstruct
22
26
  # @param [Object] value The object to coerce into a bool
23
27
  # @return [Boolean] Coerced value
24
28
  def coerce_bool(value)
25
- return false if value.nil?
26
- return false if value.to_i == 0
27
-
28
- return true
29
+ case value
30
+ when nil, false then false
31
+ when Numeric then !value.zero?
32
+ else
33
+ true
34
+ end
29
35
  end
30
36
  module_function :coerce_bool
31
37
  end
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Redstruct
2
4
  module Utils
5
+ # Adds helper methods for calling #inspect on a custom object
3
6
  module Inspectable
7
+ # Generates a human readable list of attributes when inspecting a custom object
8
+ # @return [String]
4
9
  def inspect
5
10
  attributes = inspectable_attributes.map do |key, value|
6
11
  "#{key}: <#{value.inspect}>"
@@ -8,14 +13,13 @@ module Redstruct
8
13
 
9
14
  return "#{self.class.name}: #{attributes.join(', ')}"
10
15
  end
16
+ alias to_s inspect
11
17
 
18
+ # To be overloaded by the including class
19
+ # @return [Hash<String, #inspect>] list of attributes that can be seen
12
20
  def inspectable_attributes
13
21
  {}
14
22
  end
15
-
16
- def to_s
17
- return inspect
18
- end
19
23
  end
20
24
  end
21
25
  end