redstruct 0.1.7 → 0.2.0

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 (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