kredis 0.2.3 → 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: bd8ef47e0ef8b83252c6dc0878bf81b6963a6fe959c1f81d776087a255798421
4
- data.tar.gz: 04af1db077a8ba58769bb0614830c2fd1395a3a777df1d304c6e63f48753a5bb
3
+ metadata.gz: 678d0983bf3cb50abb7732dc3c3e4da93764173dbdf03ce367254dbdb9297ef2
4
+ data.tar.gz: 7209773f020b0d1055171f30f6ff204786f3edf84e5938cd09f55789fd86e934
5
5
  SHA512:
6
- metadata.gz: cae5b52d7156a5aae222962e9f328bcd954a09aae7d24ff272319ace142cec3ac78c18b7b6bf3a46403c00fbbf0561f9f60a554384195fd7208d88518f261ddf
7
- data.tar.gz: 7b5e80312aa4daa05837697080e8ada67d9b1f59fda1f6902c9f9539caa745a703636f04d3576cb9dc86c7bbf23b8f74d0ba58b0beae7bd4aeb4e95abad180c6
6
+ metadata.gz: 2aa0b22042910eaa558f1bf8e5fed47aa7385d16ba46f63024f9311760a4f2ee74c42dadb3f333388755d1862a04999ca83cc016bf023a3e1a48491f089c7e03
7
+ data.tar.gz: ff37e6f461fc3117fd7d02116da43931550683581c6327a176f58d3f09ba06b2da538cd27976f01d7922a7ce7bf9768a8c03445b25e3dc62e635f3ac953093e0
data/README.md CHANGED
@@ -20,6 +20,23 @@ integer = Kredis.integer "myinteger"
20
20
  integer.value = 5 # => SET myinteger "5"
21
21
  5 == integer.value # => GET myinteger
22
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
39
+
23
40
  json = Kredis.json "myjson"
24
41
  json.value = { "one" => 1, "two" => "2" } # => SET myjson "{\"one\":1,\"two\":\"2\"}"
25
42
  { "one" => 1, "two" => "2" } == json.value # => GET myjson
@@ -29,87 +46,110 @@ There are data structures for counters, enums, flags, lists, unique lists, sets,
29
46
 
30
47
  ```ruby
31
48
  list = Kredis.list "mylist"
32
- list << "hello world!"
33
- [ "hello world!" ] == list.elements
49
+ list << "hello world!" # => RPUSH mylist "hello world!"
50
+ [ "hello world!" ] == list.elements # => LRANGE mylist 0, -1
34
51
 
35
52
  integer_list = Kredis.list "myintegerlist", typed: :integer
36
- integer_list.append([ 1, 2, 3 ]) # => LPUSH myintegerlist "1" "2" "3"
37
- integer_list << 4 # => LPUSH myintegerlist "4"
38
- [ 1, 2, 3, 4 ] == integer_list.elements # LRANGE 0 -1
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
39
56
 
40
57
  unique_list = Kredis.unique_list "myuniquelist"
41
- unique_list.append(%w[ 2 3 4 ])
42
- unique_list.prepend(%w[ 1 2 3 4 ])
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"
43
60
  unique_list.append([])
44
- unique_list << "5"
45
- unique_list.remove(3)
46
- [ "1", "2", "4", "5" ] == unique_list.elements
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
47
64
 
48
65
  set = Kredis.set "myset", typed: :datetime
49
- set.add(DateTime.tomorrow, DateTime.yesterday) # => SADD myset "2021-02-03 00:00:00 +0100" "2021-02-01 00:00:00 +0100"
50
- set << DateTime.tomorrow # => SADD myset "2021-02-03 00:00:00 +0100"
51
- 2 == set.size # => SCARD myset
52
- [ DateTime.tomorrow, DateTime.yesterday ] == set.elements # => SMEMBERS myset
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
53
84
 
54
85
  head_count = Kredis.counter "headcount"
55
86
  0 == head_count.value # => GET "headcount"
56
- head_count.increment
57
- head_count.increment
58
- head_count.decrement
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
59
90
  1 == head_count.value # => GET "headcount"
60
91
 
61
92
  counter = Kredis.counter "mycounter", expires_in: 5.seconds
62
- counter.increment by: 2 # => SETEX "mycounter" 900 0 + INCR "mycounter" 2
93
+ counter.increment by: 2 # => SET mycounter 0 EX 5 NX + INCRBY "mycounter" 2
63
94
  2 == counter.value # => GET "mycounter"
64
95
  sleep 6.seconds
65
96
  0 == counter.value # => GET "mycounter"
66
97
 
67
98
  cycle = Kredis.cycle "mycycle", values: %i[ one two three ]
68
- :one == cycle.value
69
- cycle.next
70
- :two == cycle.value
71
- cycle.next
72
- :three == cycle.value
73
- cycle.next
74
- :one == cycle.value
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
75
106
 
76
107
  enum = Kredis.enum "myenum", values: %w[ one two three ], default: "one"
77
- "one" == enum.value
78
- true == enum.one?
79
- enum.value = "two"
80
- "two" == enum.value
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
81
112
  enum.value = "four"
82
- "two" == enum.value
83
- enum.reset
84
- "one" == enum.value
113
+ "two" == enum.value # => GET myenum
114
+ enum.reset # => DEL myenum
115
+ "one" == enum.value # => GET myenum
85
116
 
86
117
  slots = Kredis.slots "myslots", available: 3
87
- true == slots.available?
88
- slots.reserve
89
- true == slots.available?
90
- slots.reserve
91
- true == slots.available?
92
- slots.reserve
93
- true == slots.available?
94
- slots.reserve
95
- false == slots.available?
96
- slots.release
97
- true == slots.available?
98
- slots.reset
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
99
139
 
100
140
  flag = Kredis.flag "myflag"
101
- false == flag.marked?
102
- flag.mark
103
- true == flag.marked?
104
- flag.remove
105
- false == flag.marked?
106
-
107
- flag.mark(expires_in: 1.second)
108
- true == flag.marked?
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
109
149
  sleep 0.5.seconds
110
- true == flag.marked?
150
+ true == flag.marked? #=> EXISTS myflag
111
151
  sleep 0.6.seconds
112
- false == flag.marked?
152
+ false == flag.marked? #=> EXISTS myflag
113
153
  ```
114
154
 
115
155
  And using structures on a different than the default `shared` redis instance, relying on `config/redis/secondary.yml`:
@@ -133,12 +173,23 @@ class Person < ApplicationRecord
133
173
  end
134
174
 
135
175
  person = Person.find(5)
136
- person.names.append "David", "Heinemeier", "Hansson" # => SADD person:5:names "David" "Heinemeier" "Hansson"
137
- true == person.morning.bright?
138
- person.morning.value = "blue"
139
- 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
140
180
  ```
141
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
+ ```
142
193
 
143
194
  ## Installation
144
195
 
@@ -165,6 +216,32 @@ test:
165
216
 
166
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`.
167
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
+ ```
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.
168
245
 
169
246
  ## License
170
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
31
35
  end
32
36
 
33
- def kredis_json(name, key: nil, config: :shared)
34
- kredis_connection_with __method__, name, key, config: config
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
35
39
  end
36
40
 
37
- def kredis_list(name, key: nil, typed: :string, config: :shared)
38
- kredis_connection_with __method__, name, key, typed: typed, config: config
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
39
43
  end
40
44
 
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
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
43
47
  end
44
48
 
45
- def kredis_set(name, key: nil, typed: :string, config: :shared)
46
- 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
47
51
  end
48
52
 
49
- def kredis_slot(name, key: nil, config: :shared)
50
- kredis_connection_with __method__, name, key, 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
51
55
  end
52
56
 
53
- def kredis_slots(name, available:, key: nil, config: :shared)
54
- kredis_connection_with __method__, name, key, available: available, 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
59
+ end
60
+
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
63
+ end
64
+
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
@@ -84,4 +99,11 @@ module Kredis::Attributes
84
99
  def extract_kredis_id
85
100
  try(:id) or raise NotImplementedError, "kredis needs a unique id, either implement an id method or pass a custom key."
86
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
108
+ end
87
109
  end
@@ -6,19 +6,21 @@ 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 do |connection|
17
- if Kredis.namespace
18
- keys = connection.keys("#{Kredis.namespace}:*")
19
- connection.del keys if keys.any?
20
- else
21
- connection.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
22
24
  end
23
25
  end
24
26
  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
@@ -22,8 +22,9 @@ class Kredis::Migration
22
22
  namespaced_to = Kredis.namespaced_key(to)
23
23
 
24
24
  if to.present? && from != namespaced_to
25
- log_migration "Migrating key #{from} to #{namespaced_to}"
26
- @redis.evalsha @copy_sha, keys: [ from, namespaced_to ]
25
+ log_migration "Migrating key #{from} to #{namespaced_to}" do
26
+ @redis.evalsha @copy_sha, keys: [ from, namespaced_to ]
27
+ end
27
28
  else
28
29
  log_migration "Skipping blank/unaltered migration key #{from} → #{to}"
29
30
  end
@@ -46,7 +47,7 @@ class Kredis::Migration
46
47
  end until cursor == "0"
47
48
  end
48
49
 
49
- def log_migration(message)
50
- Kredis.logger&.debug "[Kredis Migration] #{message}"
50
+ def log_migration(message, &block)
51
+ Kredis.instrument :migration, message: message, &block
51
52
  end
52
53
  end
@@ -9,7 +9,7 @@ class Kredis::Railtie < ::Rails::Railtie
9
9
  end
10
10
 
11
11
  initializer "kredis.logger" do
12
- Kredis.logger = config.kredis.logger || Rails.logger
12
+ Kredis::LogSubscriber.logger = config.kredis.logger || Rails.logger
13
13
  end
14
14
 
15
15
  initializer "kredis.configurator" do
@@ -1,3 +1,5 @@
1
+ require "json"
2
+
1
3
  module Kredis::TypeCasting
2
4
  class InvalidType < StandardError; end
3
5
 
@@ -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
@@ -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
@@ -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
@@ -14,9 +14,10 @@ class Kredis::Types::Proxy
14
14
  end
15
15
 
16
16
  def method_missing(method, *args, **kwargs)
17
- failsafe do
18
- Kredis.logger&.debug log_message(method, *args, **kwargs)
19
- 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
20
21
  end
21
22
  end
22
23
 
@@ -24,8 +25,7 @@ class Kredis::Types::Proxy
24
25
  def log_message(method, *args, **kwargs)
25
26
  args = args.flatten.reject(&:blank?).presence
26
27
  kwargs = kwargs.reject { |_k, v| v.blank? }.presence
27
- type_name = self.class.name.split("::").last
28
28
 
29
- "[Kredis #{type_name}] #{method.upcase} #{key} #{args&.inspect} #{kwargs&.inspect}".chomp
29
+ { message: "#{method.upcase} #{key} #{args&.inspect} #{kwargs&.inspect}".chomp }
30
30
  end
31
31
  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
data/lib/kredis/types.rb CHANGED
@@ -1,77 +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)
9
11
  end
10
12
 
11
- def string(key, default: nil, config: :shared)
12
- Scalar.new configured_for(config), namespaced_key(key), typed: :string, default: default
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)
13
15
  end
14
16
 
15
- def integer(key, default: nil, config: :shared)
16
- Scalar.new configured_for(config), namespaced_key(key), typed: :integer, 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)
17
19
  end
18
20
 
19
- def decimal(key, default: nil, config: :shared)
20
- Scalar.new configured_for(config), namespaced_key(key), typed: :decimal, 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)
21
23
  end
22
24
 
23
- def float(key, default: nil, config: :shared)
24
- Scalar.new configured_for(config), namespaced_key(key), typed: :float, 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)
25
27
  end
26
28
 
27
- def boolean(key, default: nil, config: :shared)
28
- Scalar.new configured_for(config), namespaced_key(key), typed: :boolean, 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)
29
31
  end
30
32
 
31
- def datetime(key, default: nil, config: :shared)
32
- Scalar.new configured_for(config), namespaced_key(key), typed: :datetime, 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)
33
35
  end
34
36
 
35
- def json(key, default: nil, config: :shared)
36
- Scalar.new configured_for(config), namespaced_key(key), typed: :json, 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)
37
39
  end
38
40
 
39
41
 
40
- def counter(key, expires_in: nil, config: :shared)
41
- Counter.new configured_for(config), namespaced_key(key), expires_in: expires_in
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)
44
+ end
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)
42
48
  end
43
49
 
44
- def cycle(key, values:, expires_in: nil, config: :shared)
45
- Cycle.new configured_for(config), namespaced_key(key), values: values, expires_in: expires_in
50
+ def flag(key, config: :shared, after_change: nil)
51
+ type_from(Flag, config, key, after_change: after_change)
46
52
  end
47
53
 
48
- def flag(key, config: :shared)
49
- 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)
50
56
  end
51
57
 
52
- def enum(key, values:, default:, config: :shared)
53
- 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)
54
60
  end
55
61
 
56
- def list(key, typed: :string, config: :shared)
57
- 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)
58
64
  end
59
65
 
60
- def unique_list(key, typed: :string, limit: nil, config: :shared)
61
- 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)
62
68
  end
63
69
 
64
- def set(key, typed: :string, config: :shared)
65
- 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)
66
72
  end
67
73
 
68
- def slot(key, config: :shared)
69
- 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)
70
76
  end
71
77
 
72
- def slots(key, available:, config: :shared)
73
- 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)
74
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
75
88
  end
76
89
 
77
90
  require "kredis/types/proxy"
@@ -82,6 +95,7 @@ require "kredis/types/counter"
82
95
  require "kredis/types/cycle"
83
96
  require "kredis/types/flag"
84
97
  require "kredis/types/enum"
98
+ require "kredis/types/hash"
85
99
  require "kredis/types/list"
86
100
  require "kredis/types/unique_list"
87
101
  require "kredis/types/set"
@@ -1,3 +1,3 @@
1
1
  module Kredis
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/kredis.rb CHANGED
@@ -4,6 +4,7 @@ require "active_support/core_ext/module/attribute_accessors"
4
4
  require "kredis/version"
5
5
 
6
6
  require "kredis/connections"
7
+ require "kredis/log_subscriber"
7
8
  require "kredis/namespace"
8
9
  require "kredis/type_casting"
9
10
  require "kredis/types"
@@ -13,12 +14,17 @@ require "kredis/railtie" if defined?(Rails::Railtie)
13
14
 
14
15
  module Kredis
15
16
  include Connections, Namespace, TypeCasting, Types
16
-
17
17
  extend self
18
18
 
19
+ autoload :Migration, "kredis/migration"
20
+
19
21
  mattr_accessor :logger
20
22
 
21
23
  def redis(config: :shared)
22
24
  configured_for(config)
23
25
  end
26
+
27
+ def instrument(channel, **options, &block)
28
+ ActiveSupport::Notifications.instrument("#{channel}.kredis", **options, &block)
29
+ end
24
30
  end
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kredis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Timm Hansen
8
8
  - David Heinemeier Hansson
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-02-11 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
  - - ">="
@@ -39,7 +39,21 @@ dependencies:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '4.2'
42
- description:
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
56
+ description:
43
57
  email: david@hey.com
44
58
  executables: []
45
59
  extensions: []
@@ -50,15 +64,18 @@ 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
59
75
  - lib/kredis/types/cycle.rb
60
76
  - lib/kredis/types/enum.rb
61
77
  - lib/kredis/types/flag.rb
78
+ - lib/kredis/types/hash.rb
62
79
  - lib/kredis/types/list.rb
63
80
  - lib/kredis/types/proxy.rb
64
81
  - lib/kredis/types/proxy/failsafe.rb
@@ -72,7 +89,7 @@ homepage: https://github.com/rails/kredis
72
89
  licenses:
73
90
  - MIT
74
91
  metadata: {}
75
- post_install_message:
92
+ post_install_message:
76
93
  rdoc_options: []
77
94
  require_paths:
78
95
  - lib
@@ -87,8 +104,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
104
  - !ruby/object:Gem::Version
88
105
  version: '0'
89
106
  requirements: []
90
- rubygems_version: 3.2.5
91
- signing_key:
107
+ rubygems_version: 3.1.4
108
+ signing_key:
92
109
  specification_version: 4
93
110
  summary: Higher-level data structures built on Redis.
94
111
  test_files: []