kredis 1.4.0 → 1.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdd8725b73c1bac9541b7b48ee62ebb46a8c51eaf78abe367fda75a12ee8c425
4
- data.tar.gz: fcee76b43f62b3bd0a99fe7d258cb1b5edab0a8d7f408f15f7e11be01a40b219
3
+ metadata.gz: 15ed6996059d28937ffa4f43a35e53d1e8fbc1894a30446575d9c00c390cd838
4
+ data.tar.gz: 296f80ea7719d69334d50240c9fce5969df4f8d976800013167c1fb0ebe6a0b3
5
5
  SHA512:
6
- metadata.gz: 1bd30168c4efb0e23f65f15eb0e0b34c0b3a62a6c871da6be82192187482f0fddc55405ffb52c12f8dad4397246b55f7d292aa3bbbd8d3dc75a4fdd2e94e009e
7
- data.tar.gz: 7dc32845d42347bcabe655447174d9cfb8b5e1d738fdf59af96557a455dc55ca52fced20fd76e514a21628dd725312120cbb6f0577e7925d6a6da585e77ffbf8
6
+ metadata.gz: cae34ebe3f21fe7c0e9cc0da5fc14bdc0d15931436bf7f07c552c14090489806990779ffd219be054f050468c809e23bf79302365f59f2dfced4b6a054ad352c
7
+ data.tar.gz: 96513897d1badc12f4fc6b2e51e017958f7e5eeaa5730d06bcb9d9a12d71ae09a4eb8ce2661e70a70647f4c4d7e5543a30451ab64d290b91de20d47602f30398
data/README.md CHANGED
@@ -21,11 +21,11 @@ integer.value = 5 # => SET myinteger "5"
21
21
  5 == integer.value # => GET myinteger
22
22
 
23
23
  decimal = Kredis.decimal "mydecimal" # accuracy!
24
- decimal.value = "%.47f" % (1.0/10) # => SET mydecimal "0.10000000000000000555111512312578270211815834045"
24
+ decimal.value = "%.47f" % (1.0 / 10) # => SET mydecimal "0.10000000000000000555111512312578270211815834045"
25
25
  BigDecimal("0.10000000000000000555111512312578270211815834045e0") == decimal.value # => GET mydecimal
26
26
 
27
27
  float = Kredis.float "myfloat" # speed!
28
- float.value = 1.0/10 # => SET myfloat "0.1"
28
+ float.value = 1.0 / 10 # => SET myfloat "0.1"
29
29
  0.1 == float.value # => GET myfloat
30
30
 
31
31
  boolean = Kredis.boolean "myboolean"
@@ -49,10 +49,10 @@ list = Kredis.list "mylist"
49
49
  list << "hello world!" # => RPUSH mylist "hello world!"
50
50
  [ "hello world!" ] == list.elements # => LRANGE mylist 0, -1
51
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
52
+ integer_list = Kredis.list "myintegerlist", typed: :integer, default: [ 1, 2, 3 ] # => EXISTS? myintegerlist, RPUSH myintegerlist "1" "2" "3"
53
+ integer_list.append([ 4, 5, 6 ]) # => RPUSH myintegerlist "4" "5" "6"
54
+ integer_list << 7 # => RPUSH myintegerlist "7"
55
+ [ 1, 2, 3, 4, 5, 6, 7 ] == integer_list.elements # => LRANGE myintegerlist 0 -1
56
56
 
57
57
  unique_list = Kredis.unique_list "myuniquelist"
58
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"
@@ -62,6 +62,14 @@ unique_list << "5" # => LREM myuniquelist 0, "5" + R
62
62
  unique_list.remove(3) # => LREM myuniquelist 0, "3"
63
63
  [ "4", "2", "1", "5" ] == unique_list.elements # => LRANGE myuniquelist 0, -1
64
64
 
65
+ ordered_set = Kredis.ordered_set "myorderedset"
66
+ ordered_set.append(%w[ 2 3 4 ]) # => ZADD myorderedset 1646131025.4953232 2 1646131025.495326 3 1646131025.4953272 4
67
+ ordered_set.prepend(%w[ 1 2 3 4 ]) # => ZADD myorderedset -1646131025.4957051 1 -1646131025.495707 2 -1646131025.4957082 3 -1646131025.4957092 4
68
+ ordered_set.append([])
69
+ ordered_set << "5" # => ZADD myorderedset 1646131025.4960442 5
70
+ ordered_set.remove(3) # => ZREM myorderedset 3
71
+ [ "4", "2", "1", "5" ] == ordered_set.elements # => ZRANGE myorderedset 0 -1
72
+
65
73
  set = Kredis.set "myset", typed: :datetime
66
74
  set.add(DateTime.tomorrow, DateTime.yesterday) # => SADD myset "2021-02-03 00:00:00 +0100" "2021-02-01 00:00:00 +0100"
67
75
  set << DateTime.tomorrow # => SADD myset "2021-02-03 00:00:00 +0100"
@@ -155,15 +163,7 @@ sleep 0.6.seconds
155
163
  false == flag.marked? #=> EXISTS myflag
156
164
  ```
157
165
 
158
- And using structures on a different than the default `shared` redis instance, relying on `config/redis/secondary.yml`:
159
-
160
- ```ruby
161
- one_string = Kredis.string "mystring"
162
- two_string = Kredis.string "mystring", config: :secondary
163
-
164
- one_string.value = "just on shared"
165
- two_string.value != one_string.value
166
- ```
166
+ ### Models
167
167
 
168
168
  You can use all these structures in models:
169
169
 
@@ -189,6 +189,29 @@ person.morning.value = "blue" # => SET people:5:morning
189
189
  true == person.morning.blue? # => GET people:5:morning
190
190
  ```
191
191
 
192
+ ### Default values
193
+
194
+ You can set a default value for all types. For example:
195
+
196
+ ```ruby
197
+ list = Kredis.list "favorite_colors", default: [ "red", "green", "blue" ]
198
+
199
+ # or, in a model
200
+ class Person < ApplicationRecord
201
+ kredis_string :name, default: "Unknown"
202
+ kredis_list :favorite_colors, default: [ "red", "green", "blue" ]
203
+ end
204
+ ```
205
+
206
+ There's a performance overhead to consider though. When you first read or write an attribute in a model, Kredis will
207
+ check if the underlying Redis key exists, while watching for concurrent changes, and if it does not,
208
+ write the specified default value.
209
+
210
+ This means that using default values in a typical Rails app additional Redis calls (WATCH, EXISTS, UNWATCH) will be
211
+ executed for each Kredis attribute with a default value read or written during a request.
212
+
213
+ ### Callbacks
214
+
192
215
  You can also define `after_change` callbacks that trigger on mutations:
193
216
 
194
217
  ```ruby
@@ -201,6 +224,18 @@ class Person < ApplicationRecord
201
224
  end
202
225
  ```
203
226
 
227
+ ### Multiple Redis servers
228
+
229
+ And using structures on a different than the default `shared` redis instance, relying on `config/redis/secondary.yml`:
230
+
231
+ ```ruby
232
+ one_string = Kredis.string "mystring"
233
+ two_string = Kredis.string "mystring", config: :secondary
234
+
235
+ one_string.value = "just on shared"
236
+ two_string.value != one_string.value
237
+ ```
238
+
204
239
  ## Installation
205
240
 
206
241
  1. Run `./bin/bundle add kredis`
@@ -220,14 +255,14 @@ If you need to connect to Redis with SSL, the recommended approach is to set you
220
255
 
221
256
  ```ruby
222
257
  Kredis::Connections.connections[:shared] = Redis.new(
223
- url: ENV['REDIS_URL'],
258
+ url: ENV["REDIS_URL"],
224
259
  ssl_params: {
225
260
  cert_store: OpenSSL::X509::Store.new.tap { |store|
226
- store.add_file(Rails.root.join('config', 'ca_cert.pem').to_s)
261
+ store.add_file(Rails.root.join("config", "ca_cert.pem").to_s)
227
262
  },
228
263
 
229
264
  cert: OpenSSL::X509::Certificate.new(File.read(
230
- Rails.root.join('config', 'client.crt')
265
+ Rails.root.join("config", "client.crt")
231
266
  )),
232
267
 
233
268
  key: OpenSSL::PKey::RSA.new(
@@ -251,6 +286,29 @@ config.kredis.connector = ->(config) { SomeRedisProxy.new(config) }
251
286
 
252
287
  By default Kredis will use `Redis.new(config)`.
253
288
 
289
+ ## Development
290
+
291
+ A development console is available by running `bin/console`.
292
+
293
+ From there, you can experiment with Kredis. e.g.
294
+
295
+ ```erb
296
+ >> str = Kredis.string "mystring"
297
+ Kredis (0.1ms) Connected to shared
298
+ =>
299
+ #<Kredis::Types::Scalar:0x0000000134c7d938
300
+ ...
301
+ >> str.value = "hello, world"
302
+ Kredis Proxy (2.4ms) SET mystring ["hello, world"]
303
+ => "hello, world"
304
+ >> str.value
305
+ ```
306
+
307
+ Run tests with `bin/test`.
308
+
309
+ [`debug`](https://github.com/ruby/debug) can be used in the development console and in the test suite by inserting a
310
+ breakpoint, e.g. `debugger`.
311
+
254
312
  ## License
255
313
 
256
314
  Kredis is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  yaml_path = Rails.root.join("config/redis/shared.yml")
2
4
  unless yaml_path.exist?
3
5
  say "Adding `config/redis/shared.yml`"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kredis::Attributes
2
4
  extend ActiveSupport::Concern
3
5
 
@@ -6,52 +8,56 @@ module Kredis::Attributes
6
8
  kredis_connection_with __method__, name, key, config: config, after_change: after_change
7
9
  end
8
10
 
9
- def kredis_string(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
10
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
11
+ def kredis_string(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
12
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
11
13
  end
12
14
 
13
- def kredis_integer(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
14
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
15
+ def kredis_integer(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
16
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
15
17
  end
16
18
 
17
- def kredis_decimal(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
18
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
19
+ def kredis_decimal(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
20
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
19
21
  end
20
22
 
21
- def kredis_datetime(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
22
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
23
+ def kredis_datetime(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
24
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
23
25
  end
24
26
 
25
- def kredis_flag(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
26
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
27
+ def kredis_flag(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
28
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
27
29
 
28
30
  define_method("#{name}?") do
29
31
  send(name).marked?
30
32
  end
31
33
  end
32
34
 
33
- def kredis_float(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
34
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
35
+ def kredis_float(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
36
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
35
37
  end
36
38
 
37
39
  def kredis_enum(name, key: nil, values:, default:, config: :shared, after_change: nil)
38
40
  kredis_connection_with __method__, name, key, values: values, default: default, config: config, after_change: after_change
39
41
  end
40
42
 
41
- def kredis_json(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
42
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
43
+ def kredis_json(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
44
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
45
+ end
46
+
47
+ def kredis_list(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil)
48
+ kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change
43
49
  end
44
50
 
45
- def kredis_list(name, key: nil, typed: :string, config: :shared, after_change: nil)
46
- kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
51
+ def kredis_unique_list(name, limit: nil, key: nil, default: nil, typed: :string, config: :shared, after_change: nil)
52
+ kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change
47
53
  end
48
54
 
49
- def kredis_unique_list(name, limit: nil, key: nil, typed: :string, config: :shared, after_change: nil)
50
- kredis_connection_with __method__, name, key, limit: limit, typed: typed, config: config, after_change: after_change
55
+ def kredis_set(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil)
56
+ kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change
51
57
  end
52
58
 
53
- def kredis_set(name, key: nil, typed: :string, config: :shared, after_change: nil)
54
- kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
59
+ def kredis_ordered_set(name, limit: nil, default: nil, key: nil, typed: :string, config: :shared, after_change: nil)
60
+ kredis_connection_with __method__, name, key, default: default, limit: limit, typed: typed, config: config, after_change: after_change
55
61
  end
56
62
 
57
63
  def kredis_slot(name, key: nil, config: :shared, after_change: nil)
@@ -62,16 +68,16 @@ module Kredis::Attributes
62
68
  kredis_connection_with __method__, name, key, available: available, config: config, after_change: after_change
63
69
  end
64
70
 
65
- def kredis_counter(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
66
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
71
+ def kredis_counter(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
72
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
67
73
  end
68
74
 
69
- def kredis_hash(name, key: nil, typed: :string, config: :shared, after_change: nil)
70
- kredis_connection_with __method__, name, key, typed: typed, config: config, after_change: after_change
75
+ def kredis_hash(name, key: nil, default: nil, typed: :string, config: :shared, after_change: nil)
76
+ kredis_connection_with __method__, name, key, default: default, typed: typed, config: config, after_change: after_change
71
77
  end
72
78
 
73
- def kredis_boolean(name, key: nil, config: :shared, after_change: nil, expires_in: nil)
74
- kredis_connection_with __method__, name, key, config: config, after_change: after_change, expires_in: expires_in
79
+ def kredis_boolean(name, key: nil, default: nil, config: :shared, after_change: nil, expires_in: nil)
80
+ kredis_connection_with __method__, name, key, default: default, config: config, after_change: after_change, expires_in: expires_in
75
81
  end
76
82
 
77
83
  private
@@ -84,6 +90,7 @@ module Kredis::Attributes
84
90
  if instance_variable_defined?(ivar_symbol)
85
91
  instance_variable_get(ivar_symbol)
86
92
  else
93
+ options[:default] = kredis_default_evaluated(options[:default]) if options[:default]
87
94
  new_type = Kredis.send(type, kredis_key_evaluated(key) || kredis_key_for_attribute(name), **options)
88
95
  instance_variable_set ivar_symbol,
89
96
  after_change ? enrich_after_change_with_record_access(new_type, after_change) : new_type
@@ -102,7 +109,7 @@ module Kredis::Attributes
102
109
  end
103
110
 
104
111
  def kredis_key_for_attribute(name)
105
- "#{self.class.name.tableize.gsub("/", ":")}:#{extract_kredis_id}:#{name}"
112
+ "#{self.class.name.tableize.tr("/", ":")}:#{extract_kredis_id}:#{name}"
106
113
  end
107
114
 
108
115
  def extract_kredis_id
@@ -115,4 +122,12 @@ module Kredis::Attributes
115
122
  when Symbol then Kredis::Types::CallbacksProxy.new(type, ->(_) { send(original_after_change) })
116
123
  end
117
124
  end
125
+
126
+ def kredis_default_evaluated(default)
127
+ case default
128
+ when Proc then Proc.new { default.call(self) }
129
+ when Symbol then send(default)
130
+ else default
131
+ end
132
+ end
118
133
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "redis"
2
4
 
3
5
  module Kredis::Connections
@@ -6,10 +8,8 @@ module Kredis::Connections
6
8
  mattr_accessor :connector, default: ->(config) { Redis.new(config) }
7
9
 
8
10
  def configured_for(name)
9
- connections[name] ||= begin
10
- Kredis.instrument :meta, message: "Connected to #{name}" do
11
- connector.call configurator.config_for("redis/#{name}")
12
- end
11
+ connections[name] ||= Kredis.instrument :meta, message: "Connected to #{name}" do
12
+ connector.call configurator.config_for("redis/#{name}")
13
13
  end
14
14
  end
15
15
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kredis::DefaultValues
4
+ extend ActiveSupport::Concern
5
+
6
+ prepended do
7
+ attr_writer :default
8
+
9
+ proxying :watch, :unwatch, :exists?
10
+
11
+ def default
12
+ case @default
13
+ when Proc then @default.call
14
+ when Symbol then send(@default)
15
+ else @default
16
+ end
17
+ end
18
+
19
+ private
20
+ def set_default
21
+ raise NotImplementedError, "Kredis type #{self.class} needs to define #set_default"
22
+ end
23
+ end
24
+
25
+ def initialize(...)
26
+ super
27
+
28
+ if default
29
+ watch do
30
+ set_default unless exists?
31
+
32
+ unwatch
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/log_subscriber"
2
4
 
3
5
  class Kredis::LogSubscriber < ActiveSupport::LogSubscriber
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/module/delegation"
2
4
 
3
5
  class Kredis::Migration
@@ -33,10 +35,10 @@ class Kredis::Migration
33
35
  def delete_all(*key_patterns)
34
36
  log_migration "DELETE ALL #{key_patterns.inspect}" do
35
37
  if key_patterns.length > 1
36
- @redis.del *key_patterns
38
+ @redis.del(*key_patterns)
37
39
  else
38
40
  each_key_batch_matching(key_patterns.first) do |keys, pipeline|
39
- pipeline.del *keys
41
+ pipeline.del(*keys)
40
42
  end
41
43
  end
42
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kredis::Namespace
2
4
  def namespace=(namespace)
3
5
  Thread.current[:kredis_namespace] = namespace
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Railtie < ::Rails::Railtie
2
4
  config.kredis = ActiveSupport::OrderedOptions.new
3
5
 
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kredis
4
+ module Type
5
+ class Boolean < ActiveModel::Type::Boolean
6
+ def serialize(value)
7
+ super ? 1 : 0
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "active_model/type"
3
- require "kredis/type/json"
5
+ require "kredis/type/boolean"
4
6
  require "kredis/type/datetime"
7
+ require "kredis/type/json"
5
8
 
6
9
  module Kredis::TypeCasting
7
10
  class InvalidType < StandardError; end
@@ -11,7 +14,7 @@ module Kredis::TypeCasting
11
14
  integer: ActiveModel::Type::Integer.new,
12
15
  decimal: ActiveModel::Type::Decimal.new,
13
16
  float: ActiveModel::Type::Float.new,
14
- boolean: ActiveModel::Type::Boolean.new,
17
+ boolean: Kredis::Type::Boolean.new,
15
18
  datetime: Kredis::Type::DateTime.new,
16
19
  json: Kredis::Type::Json.new
17
20
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::CallbacksProxy
2
4
  attr_reader :type
3
5
  delegate :to_s, to: :type
@@ -1,18 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::Counter < Kredis::Types::Proxying
4
+ prepend Kredis::DefaultValues
5
+
2
6
  proxying :multi, :set, :incrby, :decrby, :get, :del, :exists?
3
7
 
4
8
  attr_accessor :expires_in
5
9
 
6
10
  def increment(by: 1)
7
11
  multi do
8
- set 0, ex: expires_in, nx: true
12
+ set 0, ex: expires_in, nx: true if expires_in
9
13
  incrby by
10
14
  end[-1]
11
15
  end
12
16
 
13
17
  def decrement(by: 1)
14
18
  multi do
15
- set 0, ex: expires_in, nx: true
19
+ set 0, ex: expires_in, nx: true if expires_in
16
20
  decrby by
17
21
  end[-1]
18
22
  end
@@ -24,4 +28,9 @@ class Kredis::Types::Counter < Kredis::Types::Proxying
24
28
  def reset
25
29
  del
26
30
  end
31
+
32
+ private
33
+ def set_default
34
+ increment by: default
35
+ end
27
36
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::Cycle < Kredis::Types::Counter
2
4
  attr_accessor :values
3
5
 
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/object/inclusion"
2
4
 
3
5
  class Kredis::Types::Enum < Kredis::Types::Proxying
4
- proxying :set, :get, :del, :exists?
6
+ prepend Kredis::DefaultValues
7
+
8
+ InvalidDefault = Class.new(StandardError)
5
9
 
6
- attr_accessor :values, :default
10
+ proxying :set, :get, :del, :exists?, :multi
11
+
12
+ attr_accessor :values
7
13
 
8
14
  def initialize(...)
9
15
  super
@@ -17,11 +23,14 @@ class Kredis::Types::Enum < Kredis::Types::Proxying
17
23
  end
18
24
 
19
25
  def value
20
- get || default
26
+ get
21
27
  end
22
28
 
23
29
  def reset
24
- del
30
+ multi do
31
+ del
32
+ set_default
33
+ end
25
34
  end
26
35
 
27
36
  private
@@ -31,4 +40,12 @@ class Kredis::Types::Enum < Kredis::Types::Proxying
31
40
  define_singleton_method("#{defined_value}!") { self.value = defined_value }
32
41
  end
33
42
  end
43
+
44
+ def set_default
45
+ if default.in?(values) || default.nil?
46
+ set default
47
+ else
48
+ raise InvalidDefault, "Default value #{default.inspect} for #{key} is not a valid option (Valid values: #{values.join(", ")})"
49
+ end
50
+ end
34
51
  end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::Flag < Kredis::Types::Proxying
4
+ prepend Kredis::DefaultValues
5
+
2
6
  proxying :set, :exists?, :del
3
7
 
4
8
  attr_accessor :expires_in
@@ -14,4 +18,9 @@ class Kredis::Types::Flag < Kredis::Types::Proxying
14
18
  def remove
15
19
  del
16
20
  end
21
+
22
+ private
23
+ def set_default
24
+ mark if default
25
+ end
17
26
  end
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/hash"
2
4
 
3
5
  class Kredis::Types::Hash < Kredis::Types::Proxying
6
+ prepend Kredis::DefaultValues
7
+
4
8
  proxying :hget, :hset, :hmget, :hdel, :hgetall, :hkeys, :hvals, :del, :exists?
5
9
 
6
10
  attr_accessor :typed
@@ -14,7 +18,7 @@ class Kredis::Types::Hash < Kredis::Types::Proxying
14
18
  end
15
19
 
16
20
  def update(**entries)
17
- hset entries.transform_values{ |val| type_to_string(val, typed) } if entries.flatten.any?
21
+ hset entries.transform_values { |val| type_to_string(val, typed) } if entries.flatten.any?
18
22
  end
19
23
 
20
24
  def values_at(*keys)
@@ -42,4 +46,9 @@ class Kredis::Types::Hash < Kredis::Types::Proxying
42
46
  def values
43
47
  strings_to_types(hvals || [], typed)
44
48
  end
49
+
50
+ private
51
+ def set_default
52
+ update(**default)
53
+ end
45
54
  end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::List < Kredis::Types::Proxying
4
+ prepend Kredis::DefaultValues
5
+
2
6
  proxying :lrange, :lrem, :lpush, :ltrim, :rpush, :exists?, :del
3
7
 
4
8
  attr_accessor :typed
@@ -28,4 +32,9 @@ class Kredis::Types::List < Kredis::Types::Proxying
28
32
  def last(n = nil)
29
33
  n ? lrange(-n, -1) : lrange(-1, -1).first
30
34
  end
35
+
36
+ private
37
+ def set_default
38
+ append default
39
+ end
31
40
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Kredis::Types::OrderedSet < Kredis::Types::Proxying
4
+ prepend Kredis::DefaultValues
5
+
6
+ proxying :multi, :zrange, :zrem, :zadd, :zremrangebyrank, :zcard, :exists?, :del, :zscore
7
+
8
+ attr_accessor :typed
9
+ attr_reader :limit
10
+
11
+ def elements
12
+ strings_to_types(zrange(0, -1) || [], typed)
13
+ end
14
+ alias to_a elements
15
+
16
+ def remove(*elements)
17
+ zrem(types_to_strings(elements, typed))
18
+ end
19
+
20
+ def include?(element)
21
+ !!zscore(type_to_string(element, typed))
22
+ end
23
+
24
+ def prepend(elements)
25
+ insert(elements, prepending: true)
26
+ end
27
+
28
+ def append(elements)
29
+ insert(elements)
30
+ end
31
+ alias << append
32
+
33
+ def limit=(limit)
34
+ raise "Limit must be greater than 0" if limit && limit <= 0
35
+
36
+ @limit = limit
37
+ end
38
+
39
+ private
40
+ def insert(elements, prepending: false)
41
+ elements = Array(elements)
42
+ return if elements.empty?
43
+
44
+ elements_with_scores = types_to_strings(elements, typed).map.with_index do |element, index|
45
+ incremental_score = index * 0.000001
46
+
47
+ score = if prepending
48
+ -base_score - incremental_score
49
+ else
50
+ base_score + incremental_score
51
+ end
52
+
53
+ [ score, element ]
54
+ end
55
+
56
+ multi do
57
+ zadd(elements_with_scores)
58
+ trim(from_beginning: prepending)
59
+ end
60
+ end
61
+
62
+ def base_score
63
+ process_start_time + process_uptime
64
+ end
65
+
66
+ def process_start_time
67
+ @process_start_time ||= unproxied_redis.time.join(".").to_f - process_uptime
68
+ end
69
+
70
+ def process_uptime
71
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
72
+ end
73
+
74
+ def trim(from_beginning:)
75
+ return unless limit
76
+
77
+ if from_beginning
78
+ zremrangebyrank(limit, -1)
79
+ else
80
+ zremrangebyrank(0, -(limit + 1))
81
+ end
82
+ end
83
+
84
+ def set_default
85
+ append default
86
+ end
87
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kredis::Types::Proxy::Failsafe
2
4
  def initialize(*)
3
5
  super
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::Proxy
2
4
  require_relative "proxy/failsafe"
3
5
  include Failsafe
@@ -20,6 +22,16 @@ class Kredis::Types::Proxy
20
22
  end
21
23
  end
22
24
 
25
+ def watch(&block)
26
+ redis.watch(key) do
27
+ block.call
28
+ end
29
+ end
30
+
31
+ def unwatch
32
+ redis.unwatch
33
+ end
34
+
23
35
  def method_missing(method, *args, **kwargs)
24
36
  Kredis.instrument :proxy, **log_message(method, *args, **kwargs) do
25
37
  failsafe do
@@ -1,13 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/module/delegation"
2
4
 
3
5
  class Kredis::Types::Proxying
4
6
  attr_accessor :proxy, :key
5
7
 
6
8
  def self.proxying(*commands)
7
- delegate *commands, to: :proxy
9
+ delegate(*commands, to: :proxy)
8
10
  end
9
11
 
10
12
  def initialize(redis, key, **options)
13
+ @redis = redis
11
14
  @key = key
12
15
  @proxy = Kredis::Types::Proxy.new(redis, key)
13
16
  options.each { |key, value| send("#{key}=", value) }
@@ -17,6 +20,12 @@ class Kredis::Types::Proxying
17
20
  proxy.suppress_failsafe_with(returning: returning, &block)
18
21
  end
19
22
 
23
+ def unproxied_redis
24
+ # Generally, this should not be used. It's only here for the rare case where we need to
25
+ # call Redis commands that don't reference a key and don't want to be pipelined.
26
+ @redis
27
+ end
28
+
20
29
  private
21
30
  delegate :type_to_string, :string_to_type, :types_to_strings, :strings_to_types, to: :Kredis
22
31
  end
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::Scalar < Kredis::Types::Proxying
4
+ prepend Kredis::DefaultValues
5
+
2
6
  proxying :set, :get, :exists?, :del, :expire, :expireat
3
7
 
4
- attr_accessor :typed, :default, :expires_in
8
+ attr_accessor :typed, :expires_in
5
9
 
6
10
  def value=(value)
7
11
  set type_to_string(value, typed), ex: expires_in
@@ -36,4 +40,9 @@ class Kredis::Types::Scalar < Kredis::Types::Proxying
36
40
  def expire_at(datetime)
37
41
  expireat datetime.to_i
38
42
  end
43
+
44
+ private
45
+ def set_default
46
+ self.value = default
47
+ end
39
48
  end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::Set < Kredis::Types::Proxying
4
+ prepend Kredis::DefaultValues
5
+
2
6
  proxying :smembers, :sadd, :srem, :multi, :del, :sismember, :scard, :spop, :exists?, :srandmember
3
7
 
4
8
  attr_accessor :typed
@@ -47,4 +51,9 @@ class Kredis::Types::Set < Kredis::Types::Proxying
47
51
  strings_to_types(srandmember(count), typed)
48
52
  end
49
53
  end
54
+
55
+ private
56
+ def set_default
57
+ add default
58
+ end
50
59
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Kredis::Types::Slots < Kredis::Types::Proxying
2
4
  class NotAvailable < StandardError; end
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # You'd normally call this a set, but Redis already has another data type for that
2
4
  class Kredis::Types::UniqueList < Kredis::Types::List
3
5
  proxying :multi, :ltrim, :exists?
@@ -22,7 +24,7 @@ class Kredis::Types::UniqueList < Kredis::Types::List
22
24
  multi do
23
25
  remove elements
24
26
  super
25
- ltrim -limit, -1 if limit
27
+ ltrim(-limit, -1) if limit
26
28
  end
27
29
  end
28
30
  alias << append
data/lib/kredis/types.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kredis::Types
2
4
  autoload :CallbacksProxy, "kredis/types/callbacks_proxy"
3
5
 
@@ -39,36 +41,40 @@ module Kredis::Types
39
41
  end
40
42
 
41
43
 
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
+ def counter(key, expires_in: nil, default: nil, config: :shared, after_change: nil)
45
+ type_from(Counter, config, key, after_change: after_change, default: default, expires_in: expires_in)
44
46
  end
45
47
 
46
48
  def cycle(key, values:, expires_in: nil, config: :shared, after_change: nil)
47
49
  type_from(Cycle, config, key, after_change: after_change, values: values, expires_in: expires_in)
48
50
  end
49
51
 
50
- def flag(key, config: :shared, after_change: nil, expires_in: nil)
51
- type_from(Flag, config, key, after_change: after_change, expires_in: expires_in)
52
+ def flag(key, default: nil, config: :shared, after_change: nil, expires_in: nil)
53
+ type_from(Flag, config, key, after_change: after_change, default: default, expires_in: expires_in)
52
54
  end
53
55
 
54
56
  def enum(key, values:, default:, config: :shared, after_change: nil)
55
57
  type_from(Enum, config, key, after_change: after_change, values: values, default: default)
56
58
  end
57
59
 
58
- def hash(key, typed: :string, config: :shared, after_change: nil)
59
- type_from(Hash, config, key, after_change: after_change, typed: typed)
60
+ def hash(key, typed: :string, default: nil, config: :shared, after_change: nil)
61
+ type_from(Hash, config, key, after_change: after_change, default: default, typed: typed)
62
+ end
63
+
64
+ def list(key, default: nil, typed: :string, config: :shared, after_change: nil)
65
+ type_from(List, config, key, after_change: after_change, default: default, typed: typed)
60
66
  end
61
67
 
62
- def list(key, typed: :string, config: :shared, after_change: nil)
63
- type_from(List, config, key, after_change: after_change, typed: typed)
68
+ def unique_list(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil)
69
+ type_from(UniqueList, config, key, after_change: after_change, default: default, typed: typed, limit: limit)
64
70
  end
65
71
 
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)
72
+ def set(key, default: nil, typed: :string, config: :shared, after_change: nil)
73
+ type_from(Set, config, key, after_change: after_change, default: default, typed: typed)
68
74
  end
69
75
 
70
- def set(key, typed: :string, config: :shared, after_change: nil)
71
- type_from(Set, config, key, after_change: after_change, typed: typed)
76
+ def ordered_set(key, default: nil, typed: :string, limit: nil, config: :shared, after_change: nil)
77
+ type_from(OrderedSet, config, key, after_change: after_change, default: default, typed: typed, limit: limit)
72
78
  end
73
79
 
74
80
  def slot(key, config: :shared, after_change: nil)
@@ -99,4 +105,5 @@ require "kredis/types/hash"
99
105
  require "kredis/types/list"
100
106
  require "kredis/types/unique_list"
101
107
  require "kredis/types/set"
108
+ require "kredis/types/ordered_set"
102
109
  require "kredis/types/slots"
@@ -1,3 +1,3 @@
1
1
  module Kredis
2
- VERSION = "1.4.0"
2
+ VERSION = "1.6.0"
3
3
  end
data/lib/kredis.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support"
2
4
  require "active_support/core_ext/module/attribute_accessors"
3
5
  require "active_support/core_ext/module/attribute_accessors_per_thread"
@@ -8,6 +10,7 @@ require "kredis/connections"
8
10
  require "kredis/log_subscriber"
9
11
  require "kredis/namespace"
10
12
  require "kredis/type_casting"
13
+ require "kredis/default_values"
11
14
  require "kredis/types"
12
15
  require "kredis/attributes"
13
16
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :kredis do
2
4
  desc "Install kredis"
3
5
  task :install do
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: 1.4.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Timm Hansen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-06-18 00:00:00.000000000 Z
12
+ date: 2023-10-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -86,10 +86,12 @@ files:
86
86
  - lib/kredis.rb
87
87
  - lib/kredis/attributes.rb
88
88
  - lib/kredis/connections.rb
89
+ - lib/kredis/default_values.rb
89
90
  - lib/kredis/log_subscriber.rb
90
91
  - lib/kredis/migration.rb
91
92
  - lib/kredis/namespace.rb
92
93
  - lib/kredis/railtie.rb
94
+ - lib/kredis/type/boolean.rb
93
95
  - lib/kredis/type/datetime.rb
94
96
  - lib/kredis/type/json.rb
95
97
  - lib/kredis/type_casting.rb
@@ -101,6 +103,7 @@ files:
101
103
  - lib/kredis/types/flag.rb
102
104
  - lib/kredis/types/hash.rb
103
105
  - lib/kredis/types/list.rb
106
+ - lib/kredis/types/ordered_set.rb
104
107
  - lib/kredis/types/proxy.rb
105
108
  - lib/kredis/types/proxy/failsafe.rb
106
109
  - lib/kredis/types/proxying.rb
@@ -129,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
132
  - !ruby/object:Gem::Version
130
133
  version: '0'
131
134
  requirements: []
132
- rubygems_version: 3.4.10
135
+ rubygems_version: 3.4.20
133
136
  signing_key:
134
137
  specification_version: 4
135
138
  summary: Higher-level data structures built on Redis.