kredis 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of kredis might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9aa90994f3d30a19d52df3fbc284d43d2637e940de06f221ecffc2853eeda459
4
- data.tar.gz: 713137c0ec2c28bfb80df9c94b3e61c4b8f5906c9d68631fd5e26e4ac8ef0771
3
+ metadata.gz: 678d0983bf3cb50abb7732dc3c3e4da93764173dbdf03ce367254dbdb9297ef2
4
+ data.tar.gz: 7209773f020b0d1055171f30f6ff204786f3edf84e5938cd09f55789fd86e934
5
5
  SHA512:
6
- metadata.gz: afcbb020c7e7c412090a7798c433882adf7d54e293728eff3a7d39b655d41531a3bae5f307ea10faa72a39e6ed1cb1f7db1f3f6535351299d26f65fd20e728fb
7
- data.tar.gz: 7de324aa868056600c28d82911ef654c7d9f58c5d85823ab1271a7518c76f9dbb31a2be23082cb9ab2bf210401eac0ceca698412a38f01779af310bcf5893624
6
+ metadata.gz: 2aa0b22042910eaa558f1bf8e5fed47aa7385d16ba46f63024f9311760a4f2ee74c42dadb3f333388755d1862a04999ca83cc016bf023a3e1a48491f089c7e03
7
+ data.tar.gz: ff37e6f461fc3117fd7d02116da43931550683581c6327a176f58d3f09ba06b2da538cd27976f01d7922a7ce7bf9768a8c03445b25e3dc62e635f3ac953093e0
data/README.md CHANGED
@@ -2,48 +2,164 @@
2
2
 
3
3
  Kredis (Keyed Redis) encapsulates higher-level types and data structures around a single key, so you can interact with them as coherent objects rather than isolated procedural commands. These higher-level structures can be configured as attributes within Active Models and Active Records using a declarative DSL.
4
4
 
5
- Kredis is configured using env-aware yaml files, using `Rails.application.config_for`, so you can locate the data structures on separate redis instances, if you've reached a scale where a single shared instance is no longer sufficient.
5
+ Kredis is configured using env-aware YAML files, using `Rails.application.config_for`, so you can locate the data structures on separate Redis instances, if you've reached a scale where a single shared instance is no longer sufficient.
6
6
 
7
7
  Kredis provides namespacing support for keys such that you can safely run parallel testing against the data structures without different tests trampling each others data.
8
8
 
9
9
 
10
10
  ## Examples
11
11
 
12
- Kredis provides typed scalars for strings, integers, decimals, floats, booleans, datetimes, and json hashes:
12
+ Kredis provides typed scalars for strings, integers, decimals, floats, booleans, datetimes, and JSON hashes:
13
13
 
14
14
  ```ruby
15
15
  string = Kredis.string "mystring"
16
16
  string.value = "hello world!" # => SET mystring "hello world"
17
17
  "hello world!" == string.value # => GET mystring
18
18
 
19
- integer = Kredis.string "myinteger"
19
+ integer = Kredis.integer "myinteger"
20
20
  integer.value = 5 # => SET myinteger "5"
21
- 5 == string.value # => GET myinteger
21
+ 5 == integer.value # => GET myinteger
22
+
23
+ decimal = Kredis.decimal "mydecimal" # accuracy!
24
+ decimal.value = "%.47f" % (1.0/10) # => SET mydecimal "0.10000000000000000555111512312578270211815834045"
25
+ BigDecimal("0.10000000000000000555111512312578270211815834045e0") == decimal.value # => GET mydecimal
26
+
27
+ float = Kredis.float "myfloat" # speed!
28
+ float.value = 1.0/10 # => SET myfloat "0.1"
29
+ 0.1 == float.value # => GET myfloat
30
+
31
+ boolean = Kredis.boolean "myboolean"
32
+ boolean.value = true # => SET myboolean "t"
33
+ true == boolean.value # => GET myboolean
34
+
35
+ datetime = Kredis.datetime "mydatetime"
36
+ memoized_midnight = Time.zone.now.midnight
37
+ datetime.value = memoized_midnight # SET mydatetime "2021-07-27T00:00:00.000000000Z"
38
+ memoized_midnight == datetime.value # => GET mydatetime
22
39
 
23
40
  json = Kredis.json "myjson"
24
- integer.value = { "one" => 1, "two" => "2" } # => SET myjson "{\"one\":1,\"two\":\"2\"}"
25
- { "one" => 1, "two" => "2" } == string.value # => GET myjson
41
+ json.value = { "one" => 1, "two" => "2" } # => SET myjson "{\"one\":1,\"two\":\"2\"}"
42
+ { "one" => 1, "two" => "2" } == json.value # => GET myjson
26
43
  ```
27
44
 
28
- There are data structures for counters, enums, flags, lists, uniqe lists, sets, and slots:
45
+ There are data structures for counters, enums, flags, lists, unique lists, sets, and slots:
29
46
 
30
47
  ```ruby
31
- list = Kredis.list "mylist", typed: :integer
32
- list.append([ 1, 2, 3 ]) # => LPUSH mylist "1" "2" "3"
33
- list << 4 # => LPUSH mylist "4"
34
- [ 1, 2, 3, 4 ] == list.elements # LRANGE 0 -1
48
+ list = Kredis.list "mylist"
49
+ list << "hello world!" # => RPUSH mylist "hello world!"
50
+ [ "hello world!" ] == list.elements # => LRANGE mylist 0, -1
51
+
52
+ integer_list = Kredis.list "myintegerlist", typed: :integer
53
+ integer_list.append([ 1, 2, 3 ]) # => RPUSH myintegerlist "1" "2" "3"
54
+ integer_list << 4 # => RPUSH myintegerlist "4"
55
+ [ 1, 2, 3, 4 ] == integer_list.elements # => LRANGE myintegerlist 0 -1
56
+
57
+ unique_list = Kredis.unique_list "myuniquelist"
58
+ unique_list.append(%w[ 2 3 4 ]) # => LREM myuniquelist 0, "2" + LREM myuniquelist 0, "3" + LREM myuniquelist 0, "4" + RPUSH myuniquelist "2", "3", "4"
59
+ unique_list.prepend(%w[ 1 2 3 4 ]) # => LREM myuniquelist 0, "1" + LREM myuniquelist 0, "2" + LREM myuniquelist 0, "3" + LREM myuniquelist 0, "4" + LPUSH myuniquelist "1", "2", "3", "4"
60
+ unique_list.append([])
61
+ unique_list << "5" # => LREM myuniquelist 0, "5" + RPUSH myuniquelist "5"
62
+ unique_list.remove(3) # => LREM myuniquelist 0, "3"
63
+ [ "4", "2", "1", "5" ] == unique_list.elements # => LRANGE myuniquelist 0, -1
35
64
 
36
65
  set = Kredis.set "myset", typed: :datetime
37
- set.add(DateTime.tomorrow, DateTime.yesterday) # => SADD myset "2021-02-03 00:00:00 +0100" "2021-02-01 00:00:00 +0100"
38
- set << DateTime.tomorrow # => SADD myset "2021-02-03 00:00:00 +0100"
39
- 2 == set.size # => SCARD myset
40
- [ DateTime.tomorrow, DateTime.yesterday ] == set.elements # => SMEMBERS myset
41
-
42
- counter = Kredis.counter "mycounter", expires_in: 15.minutes
43
- counter.increment by: 2 # => SETEX "mycounter" 900 0 + INCR "mycounter" 2
66
+ set.add(DateTime.tomorrow, DateTime.yesterday) # => SADD myset "2021-02-03 00:00:00 +0100" "2021-02-01 00:00:00 +0100"
67
+ set << DateTime.tomorrow # => SADD myset "2021-02-03 00:00:00 +0100"
68
+ 2 == set.size # => SCARD myset
69
+ [ DateTime.tomorrow, DateTime.yesterday ] == set.members # => SMEMBERS myset
70
+
71
+ hash = Kredis.hash "myhash"
72
+ hash.update("key" => "value", "key2" => "value2") # => HSET myhash "key", "value", "key2", "value2"
73
+ { "key" => "value", "key2" => "value2" } == hash.to_h # => HGETALL myhash
74
+ "value2" == hash["key2"] # => HMGET myhash "key2"
75
+ %w[ key key2 ] == hash.keys # => HKEYS myhash
76
+ %w[ value value2 ] == hash.values # => HVALS myhash
77
+ hash.remove # => DEL myhash
78
+
79
+ high_scores = Kredis.hash "high_scores", typed: :integer
80
+ high_scores.update(space_invaders: 100, pong: 42) # HSET high_scores "space_invaders", "100", "pong", "42"
81
+ %w[ space_invaders pong ] == high_scores.keys # HKEYS high_scores
82
+ [ 100, 42 ] == high_scores.values # HVALS high_scores
83
+ { "space_invaders" => 100, "pong" => 42 } == high_scores.to_h # HGETALL high_scores
84
+
85
+ head_count = Kredis.counter "headcount"
86
+ 0 == head_count.value # => GET "headcount"
87
+ head_count.increment # => SET headcount 0 NX + INCRBY headcount 1
88
+ head_count.increment # => SET headcount 0 NX + INCRBY headcount 1
89
+ head_count.decrement # => SET headcount 0 NX + DECRBY headcount 1
90
+ 1 == head_count.value # => GET "headcount"
91
+
92
+ counter = Kredis.counter "mycounter", expires_in: 5.seconds
93
+ counter.increment by: 2 # => SET mycounter 0 EX 5 NX + INCRBY "mycounter" 2
44
94
  2 == counter.value # => GET "mycounter"
45
- travel 16.minutes
95
+ sleep 6.seconds
46
96
  0 == counter.value # => GET "mycounter"
97
+
98
+ cycle = Kredis.cycle "mycycle", values: %i[ one two three ]
99
+ :one == cycle.value # => GET mycycle
100
+ cycle.next # => GET mycycle + SET mycycle 1
101
+ :two == cycle.value # => GET mycycle
102
+ cycle.next # => GET mycycle + SET mycycle 2
103
+ :three == cycle.value # => GET mycycle
104
+ cycle.next # => GET mycycle + SET mycycle 0
105
+ :one == cycle.value # => GET mycycle
106
+
107
+ enum = Kredis.enum "myenum", values: %w[ one two three ], default: "one"
108
+ "one" == enum.value # => GET myenum
109
+ true == enum.one? # => GET myenum
110
+ enum.value = "two" # => SET myenum "two"
111
+ "two" == enum.value # => GET myenum
112
+ enum.value = "four"
113
+ "two" == enum.value # => GET myenum
114
+ enum.reset # => DEL myenum
115
+ "one" == enum.value # => GET myenum
116
+
117
+ slots = Kredis.slots "myslots", available: 3
118
+ true == slots.available? # => GET myslots
119
+ slots.reserve # => INCR myslots
120
+ true == slots.available? # => GET myslots
121
+ slots.reserve # => INCR myslots
122
+ true == slots.available? # => GET myslots
123
+ slots.reserve # => INCR myslots
124
+ false == slots.available? # => GET myslots
125
+ slots.reserve # => INCR myslots + DECR myslots
126
+ false == slots.available? # => GET myslots
127
+ slots.release # => DECR myslots
128
+ true == slots.available? # => GET myslots
129
+ slots.reset # => DEL myslots
130
+
131
+
132
+ slot = Kredis.slot "myslot"
133
+ true == slot.available? # => GET myslot
134
+ slot.reserve # => INCR myslot
135
+ false == slot.available? # => GET myslot
136
+ slot.release # => DECR myslot
137
+ true == slot.available? # => GET myslot
138
+ slot.reset # => DEL myslot
139
+
140
+ flag = Kredis.flag "myflag"
141
+ false == flag.marked? # => EXISTS myflag
142
+ flag.mark # => SET myflag 1
143
+ true == flag.marked? # => EXISTS myflag
144
+ flag.remove # => DEL myflag
145
+ false == flag.marked? # => EXISTS myflag
146
+
147
+ flag.mark(expires_in: 1.second) #=> SET myflag 1 EX 1
148
+ true == flag.marked? #=> EXISTS myflag
149
+ sleep 0.5.seconds
150
+ true == flag.marked? #=> EXISTS myflag
151
+ sleep 0.6.seconds
152
+ false == flag.marked? #=> EXISTS myflag
153
+ ```
154
+
155
+ And using structures on a different than the default `shared` redis instance, relying on `config/redis/secondary.yml`:
156
+
157
+ ```ruby
158
+ one_string = Kredis.string "mystring"
159
+ two_string = Kredis.string "mystring", config: :secondary
160
+
161
+ one_string.value = "just on shared"
162
+ two_string.value != one_string.value
47
163
  ```
48
164
 
49
165
  You can use all these structures in models:
@@ -57,12 +173,23 @@ class Person < ApplicationRecord
57
173
  end
58
174
 
59
175
  person = Person.find(5)
60
- person.names.append "David", "Heinemeier", "Hansson" # => SADD person:5:names "David" "Heinemeier" "Hansson"
61
- true == person.morning.bright?
62
- person.morning.value = "blue"
63
- true == person.morning.blue?
176
+ person.names.append "David", "Heinemeier", "Hansson" # => RPUSH people:5:names "David" "Heinemeier" "Hansson"
177
+ true == person.morning.bright? # => GET people:1:morning
178
+ person.morning.value = "blue" # => SET people:1:morning
179
+ true == person.morning.blue? # => GET people:1:morning
64
180
  ```
65
181
 
182
+ You can also define `after_change` callbacks that trigger on mutations:
183
+
184
+ ```ruby
185
+ class Person < ApplicationRecord
186
+ kredis_list :names, after_change: ->(p) { }
187
+ kredis_unique_list :skills, limit: 2, after_change: :skillset_changed
188
+
189
+ def skillset_changed
190
+ end
191
+ end
192
+ ```
66
193
 
67
194
  ## Installation
68
195
 
@@ -87,8 +214,34 @@ test:
87
214
  <<: *development
88
215
  ```
89
216
 
90
- Additional configurations can be added under `config/redis/*.yml` and referenced when a type is created.
217
+ Additional configurations can be added under `config/redis/*.yml` and referenced when a type is created, e.g. `Kredis.string("mystring", config: :strings)` would lookup `config/redis/strings.yml`. Under the hood `Kredis.configured_for` is called which'll pass the configuration on to `Redis.new`.
218
+
219
+ ### Setting SSL options on Redis Connections
220
+
221
+ If you need to connect to Redis with SSL, the recommended approach is to set your Redis instance manually by adding an entry to the `Kredis::Connections.connections` hash. Below an example showing how to connect to Redis using Client Authentication:
222
+
223
+ ```ruby
224
+ Kredis::Connections.connections[:shared] = Redis.new(
225
+ url: ENV['REDIS_URL'],
226
+ ssl_params: {
227
+ cert_store: OpenSSL::X509::Store.new.tap { |store|
228
+ store.add_file(Rails.root.join('config', 'ca_cert.pem').to_s)
229
+ },
230
+
231
+ cert: OpenSSL::X509::Certificate.new(File.read(
232
+ Rails.root.join('config', 'client.crt')
233
+ )),
234
+
235
+ key: OpenSSL::PKey::RSA.new(
236
+ Rails.application.credentials.redis[:client_key]
237
+ ),
238
+
239
+ verify_mode: OpenSSL::SSL::VERIFY_PEER
240
+ }
241
+ )
242
+ ```
91
243
 
244
+ The above code could be added to either `config/environments/production.rb` or an initializer. Please ensure that your client private key, if used, is stored your credentials file or another secure location.
92
245
 
93
246
  ## License
94
247
 
@@ -2,68 +2,83 @@ module Kredis::Attributes
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  class_methods do
5
- def kredis_proxy(name, key: nil, config: :shared)
6
- kredis_connection_with __method__, name, key, config: config
5
+ def kredis_proxy(name, key: nil, config: :shared, after_change: nil)
6
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
7
7
  end
8
8
 
9
- def kredis_string(name, key: nil, config: :shared)
10
- kredis_connection_with __method__, name, key, config: config
9
+ def kredis_string(name, key: nil, config: :shared, after_change: nil)
10
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
11
11
  end
12
12
 
13
- def kredis_integer(name, key: nil, config: :shared)
14
- kredis_connection_with __method__, name, key, config: config
13
+ def kredis_integer(name, key: nil, config: :shared, after_change: nil)
14
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
15
15
  end
16
16
 
17
- def kredis_datetime(name, key: nil, config: :shared)
18
- kredis_connection_with __method__, name, key, config: config
17
+ def kredis_decimal(name, key: nil, config: :shared, after_change: nil)
18
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
19
19
  end
20
20
 
21
- def kredis_flag(name, key: nil, config: :shared)
22
- kredis_connection_with __method__, name, key, config: config
21
+ def kredis_datetime(name, key: nil, config: :shared, after_change: nil)
22
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
23
+ end
24
+
25
+ def kredis_flag(name, key: nil, config: :shared, after_change: nil)
26
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
23
27
 
24
28
  define_method("#{name}?") do
25
29
  send(name).marked?
26
30
  end
27
31
  end
28
32
 
29
- def kredis_enum(name, key: nil, values:, default:, config: :shared)
30
- kredis_connection_with __method__, name, key, values: values, default: default, config: config
33
+ def kredis_enum(name, key: nil, values:, default:, config: :shared, after_change: nil)
34
+ kredis_connection_with __method__, name, key, values: values, default: default, config: config, after_change: after_change
35
+ end
36
+
37
+ def kredis_json(name, key: nil, config: :shared, after_change: nil)
38
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
39
+ end
40
+
41
+ def kredis_list(name, key: nil, typed: :string, config: :shared, after_change: nil)
42
+ kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
31
43
  end
32
44
 
33
- def kredis_json(name, key: nil, config: :shared)
34
- kredis_connection_with __method__, name, key, config: config
45
+ def kredis_unique_list(name, limit: nil, key: nil, typed: :string, config: :shared, after_change: nil)
46
+ kredis_connection_with __method__, name, key, limit: limit, typed: typed, config: config, after_change: after_change
35
47
  end
36
48
 
37
- def kredis_list(name, key: nil, typed: :string, config: :shared)
38
- kredis_connection_with __method__, name, key, typed: typed, config: config
49
+ def kredis_set(name, key: nil, typed: :string, config: :shared, after_change: nil)
50
+ kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
39
51
  end
40
52
 
41
- def kredis_unique_list(name, limit: nil, key: nil, typed: :string, config: :shared)
42
- kredis_connection_with __method__, name, key, limit: limit, typed: typed, config: config
53
+ def kredis_slot(name, key: nil, config: :shared, after_change: nil)
54
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
43
55
  end
44
56
 
45
- def kredis_set(name, key: nil, typed: :string, config: :shared)
46
- kredis_connection_with __method__, name, key, typed: typed, config: config
57
+ def kredis_slots(name, available:, key: nil, config: :shared, after_change: nil)
58
+ kredis_connection_with __method__, name, key, available: available, config: config, after_change: after_change
47
59
  end
48
60
 
49
- def kredis_slot(name, key: nil, config: :shared)
50
- kredis_connection_with __method__, name, key, config: config
61
+ def kredis_counter(name, key: nil, config: :shared, after_change: nil)
62
+ kredis_connection_with __method__, name, key, config: config, after_change: after_change
51
63
  end
52
64
 
53
- def kredis_slots(name, available:, key: nil, config: :shared)
54
- kredis_connection_with __method__, name, key, available: available, config: config
65
+ def kredis_hash(name, key: nil, typed: :string, config: :shared, after_change: nil)
66
+ kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
55
67
  end
56
68
 
57
69
  private
58
70
  def kredis_connection_with(method, name, key, **options)
59
71
  ivar_symbol = :"@#{name}_#{method}"
60
72
  type = method.to_s.sub("kredis_", "")
73
+ after_change = options.delete(:after_change)
61
74
 
62
75
  define_method(name) do
63
76
  if instance_variable_defined?(ivar_symbol)
64
77
  instance_variable_get(ivar_symbol)
65
78
  else
66
- instance_variable_set(ivar_symbol, Kredis.send(type, kredis_key_evaluated(key) || kredis_key_for_attribute(name), **options))
79
+ new_type = Kredis.send(type, kredis_key_evaluated(key) || kredis_key_for_attribute(name), **options)
80
+ instance_variable_set ivar_symbol,
81
+ after_change ? enrich_after_change_with_record_access(new_type, after_change) : new_type
67
82
  end
68
83
  end
69
84
  end
@@ -78,6 +93,17 @@ module Kredis::Attributes
78
93
  end
79
94
 
80
95
  def kredis_key_for_attribute(name)
81
- "#{self.class.name.tableize.gsub("/", ":")}:#{id}:#{name}"
96
+ "#{self.class.name.tableize.gsub("/", ":")}:#{extract_kredis_id}:#{name}"
97
+ end
98
+
99
+ def extract_kredis_id
100
+ try(:id) or raise NotImplementedError, "kredis needs a unique id, either implement an id method or pass a custom key."
101
+ end
102
+
103
+ def enrich_after_change_with_record_access(type, original_after_change)
104
+ case original_after_change
105
+ when Proc then Kredis::Types::CallbacksProxy.new(type, ->(_) { original_after_change.call(self) })
106
+ when Symbol then Kredis::Types::CallbacksProxy.new(type, ->(_) { send(original_after_change) })
107
+ end
82
108
  end
83
109
  end
@@ -6,13 +6,22 @@ module Kredis::Connections
6
6
 
7
7
  def configured_for(name)
8
8
  connections[name] ||= begin
9
- logger&.info "[Kredis] Connected to #{name}"
10
- Redis.new configurator.config_for("redis/#{name}")
9
+ Kredis.instrument :meta, message: "Connected to #{name}" do
10
+ Redis.new configurator.config_for("redis/#{name}")
11
+ end
11
12
  end
12
13
  end
13
14
 
14
15
  def clear_all
15
- logger&.info "[Kredis] Connections all cleared"
16
- connections.each_value(&:flushdb)
16
+ Kredis.instrument :meta, message: "Connections all cleared" do
17
+ connections.each_value do |connection|
18
+ if Kredis.namespace
19
+ keys = connection.keys("#{Kredis.namespace}:*")
20
+ connection.del keys if keys.any?
21
+ else
22
+ connection.flushdb
23
+ end
24
+ end
25
+ end
17
26
  end
18
27
  end
@@ -0,0 +1,22 @@
1
+ require "active_support/log_subscriber"
2
+
3
+ class Kredis::LogSubscriber < ActiveSupport::LogSubscriber
4
+ def proxy(event)
5
+ debug formatted_in(YELLOW, event, type: "Proxy")
6
+ end
7
+
8
+ def migration(event)
9
+ debug formatted_in(YELLOW, event, type: "Migration")
10
+ end
11
+
12
+ def meta(event)
13
+ info formatted_in(MAGENTA, event)
14
+ end
15
+
16
+ private
17
+ def formatted_in(color, event, type: nil)
18
+ color " Kredis #{type} (#{event.duration.round(1)}ms) #{event.payload[:message]}", color, true
19
+ end
20
+ end
21
+
22
+ Kredis::LogSubscriber.attach_to :kredis
@@ -1,47 +1,53 @@
1
1
  require "active_support/core_ext/module/delegation"
2
2
 
3
3
  class Kredis::Migration
4
- singleton_class.delegate :migrate_all, :migrate, to: :new
4
+ singleton_class.delegate :migrate_all, :migrate, :delete_all, to: :new
5
5
 
6
6
  def initialize(config = :shared)
7
7
  @redis = Kredis.configured_for config
8
+ # TODO: Replace script loading with `copy` command once Redis 6.2+ is the minimum supported version.
8
9
  @copy_sha = @redis.script "load", "redis.call('SETNX', KEYS[2], redis.call('GET', KEYS[1])); return 1;"
9
10
  end
10
11
 
11
12
  def migrate_all(key_pattern)
12
- find_keys_in_batches_matching(key_pattern) do |keys|
13
- @redis.multi do
14
- keys.each do |key|
15
- ids = key.scan(/\d+/).map(&:to_i)
16
- migrate from: key, to: yield(key, *ids)
17
- end
13
+ each_key_batch_matching(key_pattern) do |keys|
14
+ keys.each do |key|
15
+ ids = key.scan(/\d+/).map(&:to_i)
16
+ migrate from: key, to: yield(key, *ids)
18
17
  end
19
18
  end
20
19
  end
21
20
 
22
21
  def migrate(from:, to:)
23
- to = Kredis.namespaced_key(to)
22
+ namespaced_to = Kredis.namespaced_key(to)
24
23
 
25
- if from != to
26
- log_migration "Migrating key #{from} to #{to}"
27
- @redis.evalsha @copy_sha, keys: [ from, to ]
24
+ if to.present? && from != namespaced_to
25
+ log_migration "Migrating key #{from} to #{namespaced_to}" do
26
+ @redis.evalsha @copy_sha, keys: [ from, namespaced_to ]
27
+ end
28
28
  else
29
- log_migration "Skipping unaltered migration key #{from}"
29
+ log_migration "Skipping blank/unaltered migration key #{from} → #{to}"
30
+ end
31
+ end
32
+
33
+ def delete_all(key_pattern)
34
+ each_key_batch_matching(key_pattern) do |keys|
35
+ @redis.del *keys
30
36
  end
31
37
  end
32
38
 
33
39
  private
34
40
  SCAN_BATCH_SIZE = 1_000
35
41
 
36
- def find_keys_in_batches_matching(key_pattern, &block)
42
+ def each_key_batch_matching(key_pattern, &block)
37
43
  cursor = "0"
38
44
  begin
39
45
  cursor, keys = @redis.scan(cursor, match: key_pattern, count: SCAN_BATCH_SIZE)
40
- yield keys
46
+ @redis.pipelined { yield keys }
41
47
  end until cursor == "0"
42
48
  end
43
49
 
44
- def log_migration(message)
45
- Kredis.logger&.debug "[Kredis Migration] #{message}"
50
+ def log_migration(message, &block)
51
+ Kredis.instrument :migration, message: message, &block
46
52
  end
47
53
  end
@@ -1,33 +1,29 @@
1
- require "rails/railtie"
1
+ class Kredis::Railtie < ::Rails::Railtie
2
+ config.kredis = ActiveSupport::OrderedOptions.new
2
3
 
3
- module Kredis
4
- class Railtie < ::Rails::Railtie
5
- config.kredis = ActiveSupport::OrderedOptions.new
6
-
7
- initializer "kredis.testing" do
8
- ActiveSupport.on_load(:active_support_test_case) do
9
- parallelize_setup { |worker| Kredis.namespace = "test-#{worker}" }
10
- parallelize_teardown { Kredis.clear_all }
11
- end
4
+ initializer "kredis.testing" do
5
+ ActiveSupport.on_load(:active_support_test_case) do
6
+ parallelize_setup { |worker| Kredis.namespace = "test-#{worker}" }
7
+ teardown { Kredis.clear_all }
12
8
  end
9
+ end
13
10
 
14
- initializer "kredis.logger" do
15
- Kredis.logger = config.kredis.logger || Rails.logger
16
- end
11
+ initializer "kredis.logger" do
12
+ Kredis::LogSubscriber.logger = config.kredis.logger || Rails.logger
13
+ end
17
14
 
18
- initializer "kredis.configurator" do
19
- Kredis.configurator = Rails.application
20
- end
15
+ initializer "kredis.configurator" do
16
+ Kredis.configurator = Rails.application
17
+ end
21
18
 
22
- initializer "kredis.attributes" do
23
- # TODO: Add run_load_hooks to ActiveModel::Model so this runs.
24
- ActiveSupport.on_load(:active_model) do
25
- include Kredis::Attributes
26
- end
19
+ initializer "kredis.attributes" do
20
+ # No load hook for Active Model, just defer until after initialization.
21
+ config.after_initialize do
22
+ ActiveModel::Model.include Kredis::Attributes if defined?(ActiveModel::Model)
23
+ end
27
24
 
28
- ActiveSupport.on_load(:active_record) do
29
- include Kredis::Attributes
30
- end
25
+ ActiveSupport.on_load(:active_record) do
26
+ include Kredis::Attributes
31
27
  end
32
28
  end
33
29
  end
@@ -1,3 +1,5 @@
1
+ require "json"
2
+
1
3
  module Kredis::TypeCasting
2
4
  class InvalidType < StandardError; end
3
5
 
@@ -16,7 +18,7 @@ module Kredis::TypeCasting
16
18
  when TrueClass, FalseClass
17
19
  value ? "t" : "f"
18
20
  when Time, DateTime, ActiveSupport::TimeWithZone
19
- value.to_f
21
+ value.iso8601(9)
20
22
  when Hash
21
23
  JSON.dump(value)
22
24
  else
@@ -25,7 +27,7 @@ module Kredis::TypeCasting
25
27
  end
26
28
 
27
29
  def string_to_type(value, type)
28
- raise InvalidType if type && !type.in?(VALID_TYPES)
30
+ raise InvalidType if type && !VALID_TYPES.include?(type)
29
31
 
30
32
  case type
31
33
  when nil, :string then value
@@ -33,7 +35,7 @@ module Kredis::TypeCasting
33
35
  when :decimal then value.to_d
34
36
  when :float then value.to_f
35
37
  when :boolean then value == "t" ? true : false
36
- when :datetime then Time.at(value.to_i)
38
+ when :datetime then Time.iso8601(value)
37
39
  when :json then JSON.load(value)
38
40
  end if value.present?
39
41
  end
@@ -0,0 +1,31 @@
1
+ class Kredis::Types::CallbacksProxy
2
+ attr_reader :type
3
+ delegate :to_s, to: :type
4
+
5
+ AFTER_CHANGE_OPERATIONS = {
6
+ Kredis::Types::Counter => %i[ increment decrement reset ],
7
+ Kredis::Types::Cycle => %i[ next ],
8
+ Kredis::Types::Enum => %i[ value= reset ],
9
+ Kredis::Types::Flag => %i[ mark remove ],
10
+ Kredis::Types::Hash => %i[ update delete ],
11
+ Kredis::Types::List => %i[ remove prepend append << ],
12
+ Kredis::Types::Scalar => %i[ value= clear ],
13
+ Kredis::Types::Set => %i[ add << remove replace take clear ],
14
+ Kredis::Types::Slots => %i[ reserve release reset ]
15
+ }
16
+
17
+ def initialize(type, callback)
18
+ @type, @callback = type, callback
19
+ end
20
+
21
+ def method_missing(method, *args, **kwargs, &block)
22
+ result = type.send(method, *args, **kwargs, &block)
23
+ invoke_suitable_after_change_callback_for method
24
+ result
25
+ end
26
+
27
+ private
28
+ def invoke_suitable_after_change_callback_for(method)
29
+ @callback.call(type) if AFTER_CHANGE_OPERATIONS[type.class]&.include? method
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  class Kredis::Types::Counter < Kredis::Types::Proxying
2
- proxying :multi, :set, :incrby, :get
2
+ proxying :multi, :set, :incrby, :decrby, :get, :del
3
3
 
4
4
  attr_accessor :expires_in
5
5
 
@@ -10,7 +10,18 @@ class Kredis::Types::Counter < Kredis::Types::Proxying
10
10
  end
11
11
  end
12
12
 
13
+ def decrement(by: 1)
14
+ multi do
15
+ set 0, ex: expires_in, nx: true
16
+ decrby by
17
+ end
18
+ end
19
+
13
20
  def value
14
21
  get.to_i
15
22
  end
23
+
24
+ def reset
25
+ del
26
+ end
16
27
  end
@@ -0,0 +1,13 @@
1
+ class Kredis::Types::Cycle < Kredis::Types::Counter
2
+ attr_accessor :values
3
+
4
+ alias index value
5
+
6
+ def value
7
+ values[index]
8
+ end
9
+
10
+ def next
11
+ set (index + 1) % values.size
12
+ end
13
+ end
@@ -0,0 +1,45 @@
1
+ require "active_support/core_ext/hash"
2
+
3
+ class Kredis::Types::Hash < Kredis::Types::Proxying
4
+ proxying :hget, :hset, :hmget, :hdel, :hgetall, :hkeys, :hvals, :del
5
+
6
+ attr_accessor :typed
7
+
8
+ def [](key)
9
+ string_to_type(hget(key), typed)
10
+ end
11
+
12
+ def []=(key, value)
13
+ update key => value
14
+ end
15
+
16
+
17
+ def update(**entries)
18
+ hset types_to_strings(entries) if entries.flatten.any?
19
+ end
20
+
21
+ def values_at(*keys)
22
+ strings_to_types(hmget(keys) || [], typed)
23
+ end
24
+
25
+ def delete(*keys)
26
+ hdel types_to_strings(keys) if keys.flatten.any?
27
+ end
28
+
29
+ def remove
30
+ del
31
+ end
32
+
33
+ def entries
34
+ (hgetall || {}).transform_values { |val| string_to_type(val, typed) }.with_indifferent_access
35
+ end
36
+ alias to_h entries
37
+
38
+ def keys
39
+ hkeys || []
40
+ end
41
+
42
+ def values
43
+ strings_to_types(hvals || [], typed)
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ module Kredis::Types::Proxy::Failsafe
2
+ def initialize(*)
3
+ super
4
+ @fail_safe_suppressed = false
5
+ end
6
+
7
+ def failsafe
8
+ yield
9
+ rescue Redis::BaseError
10
+ raise if fail_safe_suppressed?
11
+ end
12
+
13
+ def suppress_failsafe_with(returning: nil)
14
+ old_fail_safe_suppressed, @fail_safe_suppressed = @fail_safe_suppressed, true
15
+ yield
16
+ rescue Redis::BaseError
17
+ returning
18
+ ensure
19
+ @fail_safe_suppressed = old_fail_safe_suppressed
20
+ end
21
+
22
+ private
23
+ def fail_safe_suppressed?
24
+ @fail_safe_suppressed
25
+ end
26
+ end
@@ -1,4 +1,7 @@
1
1
  class Kredis::Types::Proxy
2
+ require_relative "proxy/failsafe"
3
+ include Failsafe
4
+
2
5
  attr_accessor :redis, :key
3
6
 
4
7
  def initialize(redis, key, **options)
@@ -11,16 +14,18 @@ class Kredis::Types::Proxy
11
14
  end
12
15
 
13
16
  def method_missing(method, *args, **kwargs)
14
- Kredis.logger&.debug log_message(method, *args, **kwargs)
15
- redis.public_send method, key, *args, **kwargs
17
+ Kredis.instrument :proxy, **log_message(method, *args, **kwargs) do
18
+ failsafe do
19
+ redis.public_send method, key, *args, **kwargs
20
+ end
21
+ end
16
22
  end
17
23
 
18
24
  private
19
25
  def log_message(method, *args, **kwargs)
20
- args = args.flatten.compact_blank.presence
21
- kwargs = kwargs.compact_blank.presence
22
- type_name = self.class.name.split("::").last
26
+ args = args.flatten.reject(&:blank?).presence
27
+ kwargs = kwargs.reject { |_k, v| v.blank? }.presence
23
28
 
24
- "[Kredis #{type_name}] #{method.upcase} #{key} #{args&.inspect} #{kwargs&.inspect}".chomp
29
+ { message: "#{method.upcase} #{key} #{args&.inspect} #{kwargs&.inspect}".chomp }
25
30
  end
26
31
  end
@@ -13,6 +13,10 @@ class Kredis::Types::Proxying
13
13
  options.each { |key, value| send("#{key}=", value) }
14
14
  end
15
15
 
16
+ def failsafe(returning: nil, &block)
17
+ proxy.suppress_failsafe_with(returning: returning, &block)
18
+ end
19
+
16
20
  private
17
21
  delegate :type_to_string, :string_to_type, :types_to_strings, :strings_to_types, to: :Kredis
18
22
  end
@@ -1,14 +1,20 @@
1
1
  class Kredis::Types::Scalar < Kredis::Types::Proxying
2
- proxying :set, :get, :exists?, :del
2
+ proxying :set, :get, :exists?, :del, :expire, :expireat
3
3
 
4
- attr_accessor :typed, :default
4
+ attr_accessor :typed, :default, :expires_in
5
5
 
6
6
  def value=(value)
7
- set type_to_string(value)
7
+ set type_to_string(value), ex: expires_in
8
8
  end
9
9
 
10
10
  def value
11
- string_to_type(get, typed) || default
11
+ value_after_casting = string_to_type(get, typed)
12
+
13
+ if value_after_casting.nil?
14
+ default
15
+ else
16
+ value_after_casting
17
+ end
12
18
  end
13
19
 
14
20
  def to_s
@@ -22,4 +28,12 @@ class Kredis::Types::Scalar < Kredis::Types::Proxying
22
28
  def clear
23
29
  del
24
30
  end
31
+
32
+ def expire_in(seconds)
33
+ expire seconds.to_i
34
+ end
35
+
36
+ def expire_at(datetime)
37
+ expireat datetime.to_i
38
+ end
25
39
  end
@@ -29,7 +29,7 @@ class Kredis::Types::Set < Kredis::Types::Proxying
29
29
  end
30
30
 
31
31
  def size
32
- scard
32
+ scard.to_i
33
33
  end
34
34
 
35
35
  def take
@@ -6,23 +6,25 @@ class Kredis::Types::Slots < Kredis::Types::Proxying
6
6
  attr_accessor :available
7
7
 
8
8
  def reserve
9
- if block_given?
10
- begin
11
- if reserve
12
- yield
9
+ failsafe returning: false do
10
+ if block_given?
11
+ begin
12
+ if reserve
13
+ yield
14
+ true
15
+ else
16
+ false
17
+ end
18
+ ensure
19
+ release
20
+ end
21
+ else
22
+ if incr <= available
13
23
  true
14
24
  else
25
+ release
15
26
  false
16
27
  end
17
- ensure
18
- release
19
- end
20
- else
21
- if incr <= available
22
- true
23
- else
24
- release
25
- false
26
28
  end
27
29
  end
28
30
  end
@@ -32,7 +34,9 @@ class Kredis::Types::Slots < Kredis::Types::Proxying
32
34
  end
33
35
 
34
36
  def available?
35
- get.to_i < available
37
+ failsafe returning: false do
38
+ get.to_i < available
39
+ end
36
40
  end
37
41
 
38
42
  def reset
data/lib/kredis/types.rb CHANGED
@@ -1,73 +1,90 @@
1
1
  module Kredis::Types
2
- def proxy(key, config: :shared)
3
- Proxy.new configured_for(config), namespaced_key(key)
2
+ autoload :CallbacksProxy, "kredis/types/callbacks_proxy"
3
+
4
+ def proxy(key, config: :shared, after_change: nil)
5
+ type_from(Proxy, config, key, after_change: after_change)
4
6
  end
5
7
 
6
8
 
7
- def scalar(key, typed: :string, default: nil, config: :shared)
8
- Scalar.new configured_for(config), namespaced_key(key), typed: typed, default: default
9
+ def scalar(key, typed: :string, default: nil, config: :shared, after_change: nil, expires_in: nil)
10
+ type_from(Scalar, config, key, after_change: after_change, typed: typed, default: default, expires_in: expires_in)
11
+ end
12
+
13
+ def string(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
14
+ type_from(Scalar, config, key, after_change: after_change, typed: :string, default: default, expires_in: expires_in)
9
15
  end
10
16
 
11
- def string(key, default: nil, config: :shared)
12
- Scalar.new configured_for(config), namespaced_key(key), typed: :string, default: default
17
+ def integer(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
18
+ type_from(Scalar, config, key, after_change: after_change, typed: :integer, default: default, expires_in: expires_in)
13
19
  end
14
20
 
15
- def integer(key, default: nil, config: :shared)
16
- Scalar.new configured_for(config), namespaced_key(key), typed: :integer, default: default
21
+ def decimal(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
22
+ type_from(Scalar, config, key, after_change: after_change, typed: :decimal, default: default, expires_in: expires_in)
17
23
  end
18
24
 
19
- def decimal(key, default: nil, config: :shared)
20
- Scalar.new configured_for(config), namespaced_key(key), typed: :decimal, default: default
25
+ def float(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
26
+ type_from(Scalar, config, key, after_change: after_change, typed: :float, default: default, expires_in: expires_in)
21
27
  end
22
28
 
23
- def float(key, default: nil, config: :shared)
24
- Scalar.new configured_for(config), namespaced_key(key), typed: :float, default: default
29
+ def boolean(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
30
+ type_from(Scalar, config, key, after_change: after_change, typed: :boolean, default: default, expires_in: expires_in)
25
31
  end
26
32
 
27
- def boolean(key, default: nil, config: :shared)
28
- Scalar.new configured_for(config), namespaced_key(key), typed: :boolean, default: default
33
+ def datetime(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
34
+ type_from(Scalar, config, key, after_change: after_change, typed: :datetime, default: default, expires_in: expires_in)
29
35
  end
30
36
 
31
- def datetime(key, default: nil, config: :shared)
32
- Scalar.new configured_for(config), namespaced_key(key), typed: :datetime, default: default
37
+ def json(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
38
+ type_from(Scalar, config, key, after_change: after_change, typed: :json, default: default, expires_in: expires_in)
33
39
  end
34
40
 
35
- def json(key, default: nil, config: :shared)
36
- Scalar.new configured_for(config), namespaced_key(key), typed: :json, default: default
41
+
42
+ def counter(key, expires_in: nil, config: :shared, after_change: nil)
43
+ type_from(Counter, config, key, after_change: after_change, expires_in: expires_in)
37
44
  end
38
45
 
46
+ def cycle(key, values:, expires_in: nil, config: :shared, after_change: nil)
47
+ type_from(Cycle, config, key, after_change: after_change, values: values, expires_in: expires_in)
48
+ end
39
49
 
40
- def counter(key, expires_in: nil, config: :shared)
41
- Counter.new configured_for(config), namespaced_key(key), expires_in: expires_in
50
+ def flag(key, config: :shared, after_change: nil)
51
+ type_from(Flag, config, key, after_change: after_change)
42
52
  end
43
53
 
44
- def flag(key, config: :shared)
45
- Flag.new configured_for(config), namespaced_key(key)
54
+ def enum(key, values:, default:, config: :shared, after_change: nil)
55
+ type_from(Enum, config, key, after_change: after_change, values: values, default: default)
46
56
  end
47
57
 
48
- def enum(key, values:, default:, config: :shared)
49
- Enum.new configured_for(config), namespaced_key(key), values: values, default: default
58
+ def hash(key, typed: :string, config: :shared, after_change: nil)
59
+ type_from(Hash, config, key, after_change: after_change, typed: typed)
50
60
  end
51
61
 
52
- def list(key, typed: :string, config: :shared)
53
- List.new configured_for(config), namespaced_key(key), typed: typed
62
+ def list(key, typed: :string, config: :shared, after_change: nil)
63
+ type_from(List, config, key, after_change: after_change, typed: typed)
54
64
  end
55
65
 
56
- def unique_list(key, typed: :string, limit: nil, config: :shared)
57
- UniqueList.new configured_for(config), namespaced_key(key), typed: typed, limit: limit
66
+ def unique_list(key, typed: :string, limit: nil, config: :shared, after_change: nil)
67
+ type_from(UniqueList, config, key, after_change: after_change, typed: typed, limit: limit)
58
68
  end
59
69
 
60
- def set(key, typed: :string, config: :shared)
61
- Set.new configured_for(config), namespaced_key(key), typed: typed
70
+ def set(key, typed: :string, config: :shared, after_change: nil)
71
+ type_from(Set, config, key, after_change: after_change, typed: typed)
62
72
  end
63
73
 
64
- def slot(key, config: :shared)
65
- Slots.new configured_for(config), namespaced_key(key), available: 1
74
+ def slot(key, config: :shared, after_change: nil)
75
+ type_from(Slots, config, key, after_change: after_change, available: 1)
66
76
  end
67
77
 
68
- def slots(key, available:, config: :shared)
69
- Slots.new configured_for(config), namespaced_key(key), available: available
78
+ def slots(key, available:, config: :shared, after_change: nil)
79
+ type_from(Slots, config, key, after_change: after_change, available: available)
70
80
  end
81
+
82
+ private
83
+ def type_from(type_klass, config, key, after_change: nil, **options)
84
+ type_klass.new(configured_for(config), namespaced_key(key), **options).then do |type|
85
+ after_change ? CallbacksProxy.new(type, after_change) : type
86
+ end
87
+ end
71
88
  end
72
89
 
73
90
  require "kredis/types/proxy"
@@ -75,8 +92,10 @@ require "kredis/types/proxying"
75
92
 
76
93
  require "kredis/types/scalar"
77
94
  require "kredis/types/counter"
95
+ require "kredis/types/cycle"
78
96
  require "kredis/types/flag"
79
97
  require "kredis/types/enum"
98
+ require "kredis/types/hash"
80
99
  require "kredis/types/list"
81
100
  require "kredis/types/unique_list"
82
101
  require "kredis/types/set"
@@ -1,3 +1,3 @@
1
1
  module Kredis
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/kredis.rb CHANGED
@@ -1,20 +1,30 @@
1
- require "kredis/railtie"
1
+ require "active_support"
2
+ require "active_support/core_ext/module/attribute_accessors"
3
+
2
4
  require "kredis/version"
3
5
 
4
6
  require "kredis/connections"
7
+ require "kredis/log_subscriber"
5
8
  require "kredis/namespace"
9
+ require "kredis/type_casting"
6
10
  require "kredis/types"
7
11
  require "kredis/attributes"
8
- require "kredis/type_casting"
9
12
 
10
- module Kredis
11
- include Connections, Namespace, Types, TypeCasting
13
+ require "kredis/railtie" if defined?(Rails::Railtie)
12
14
 
15
+ module Kredis
16
+ include Connections, Namespace, TypeCasting, Types
13
17
  extend self
14
18
 
19
+ autoload :Migration, "kredis/migration"
20
+
15
21
  mattr_accessor :logger
16
22
 
17
23
  def redis(config: :shared)
18
24
  configured_for(config)
19
25
  end
26
+
27
+ def instrument(channel, **options, &block)
28
+ ActiveSupport::Notifications.instrument("#{channel}.kredis", **options, &block)
29
+ end
20
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kredis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Timm Hansen
@@ -9,10 +9,10 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-02-02 00:00:00.000000000 Z
12
+ date: 2021-09-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rails
15
+ name: activesupport
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - ">="
@@ -31,14 +31,28 @@ dependencies:
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '4.0'
34
+ version: '4.2'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '4.0'
41
+ version: '4.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rails
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 6.0.0
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 6.0.0
42
56
  description:
43
57
  email: david@hey.com
44
58
  executables: []
@@ -50,16 +64,21 @@ files:
50
64
  - lib/kredis.rb
51
65
  - lib/kredis/attributes.rb
52
66
  - lib/kredis/connections.rb
67
+ - lib/kredis/log_subscriber.rb
53
68
  - lib/kredis/migration.rb
54
69
  - lib/kredis/namespace.rb
55
70
  - lib/kredis/railtie.rb
56
71
  - lib/kredis/type_casting.rb
57
72
  - lib/kredis/types.rb
73
+ - lib/kredis/types/callbacks_proxy.rb
58
74
  - lib/kredis/types/counter.rb
75
+ - lib/kredis/types/cycle.rb
59
76
  - lib/kredis/types/enum.rb
60
77
  - lib/kredis/types/flag.rb
78
+ - lib/kredis/types/hash.rb
61
79
  - lib/kredis/types/list.rb
62
80
  - lib/kredis/types/proxy.rb
81
+ - lib/kredis/types/proxy/failsafe.rb
63
82
  - lib/kredis/types/proxying.rb
64
83
  - lib/kredis/types/scalar.rb
65
84
  - lib/kredis/types/set.rb
@@ -78,14 +97,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
97
  requirements:
79
98
  - - ">="
80
99
  - !ruby/object:Gem::Version
81
- version: 2.6.0
100
+ version: 2.7.0
82
101
  required_rubygems_version: !ruby/object:Gem::Requirement
83
102
  requirements:
84
103
  - - ">="
85
104
  - !ruby/object:Gem::Version
86
105
  version: '0'
87
106
  requirements: []
88
- rubygems_version: 3.1.2
107
+ rubygems_version: 3.1.4
89
108
  signing_key:
90
109
  specification_version: 4
91
110
  summary: Higher-level data structures built on Redis.