kredis 0.1.0 → 0.2.2
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 +4 -4
- data/README.md +153 -3
- data/lib/kredis.rb +10 -4
- data/lib/kredis/attributes.rb +71 -18
- data/lib/kredis/connections.rb +5 -1
- data/lib/kredis/migration.rb +51 -0
- data/lib/kredis/railtie.rb +10 -6
- data/lib/kredis/type_casting.rb +48 -0
- data/lib/kredis/types.rb +67 -11
- data/lib/kredis/types/counter.rb +12 -6
- data/lib/kredis/types/enum.rb +31 -0
- data/lib/kredis/types/flag.rb +15 -0
- data/lib/kredis/types/list.rb +14 -8
- data/lib/kredis/types/proxy.rb +31 -0
- data/lib/kredis/types/proxy/failsafe.rb +26 -0
- data/lib/kredis/types/proxying.rb +22 -0
- data/lib/kredis/types/scalar.rb +25 -0
- data/lib/kredis/types/set.rb +42 -0
- data/lib/kredis/types/slots.rb +45 -0
- data/lib/kredis/types/unique_list.rb +8 -8
- data/lib/kredis/version.rb +1 -1
- metadata +19 -11
- data/lib/kredis/proxy.rb +0 -13
- data/lib/kredis/types/mutex.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3e3619ec3a977b472348572202b324c813d550d43cc866ec1aab7dad8e463f0
|
4
|
+
data.tar.gz: 920c49f0cfe443a71dd5dc8f112070ab8061bbe2d352c98b45026ceeed871f2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f97bda6b251a100411c0338d2760aa1c2565f3ee1901f5f4971123cbe8b7a49fabf64ccc210775e9694263fd1a2a142641bcf81659776f746d05163640afda5
|
7
|
+
data.tar.gz: 546b9246e3952a92452fe55cf574233b12d50f29bdeb345c7efe3ebf86bd3ab9a6ff4d22731438ae3258586caca530c1e7229ffee9b581a4a22b04f585dd4467
|
data/README.md
CHANGED
@@ -1,11 +1,161 @@
|
|
1
1
|
# Kredis
|
2
2
|
|
3
|
-
Keyed Redis encapsulates higher-level data structures around a single key, so you can interact with them as coherent objects rather than isolated procedural commands.
|
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
6
|
|
6
|
-
|
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
|
+
|
9
|
+
|
10
|
+
## Examples
|
11
|
+
|
12
|
+
Kredis provides typed scalars for strings, integers, decimals, floats, booleans, datetimes, and JSON hashes:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
string = Kredis.string "mystring"
|
16
|
+
string.value = "hello world!" # => SET mystring "hello world"
|
17
|
+
"hello world!" == string.value # => GET mystring
|
18
|
+
|
19
|
+
integer = Kredis.integer "myinteger"
|
20
|
+
integer.value = 5 # => SET myinteger "5"
|
21
|
+
5 == integer.value # => GET myinteger
|
22
|
+
|
23
|
+
json = Kredis.json "myjson"
|
24
|
+
json.value = { "one" => 1, "two" => "2" } # => SET myjson "{\"one\":1,\"two\":\"2\"}"
|
25
|
+
{ "one" => 1, "two" => "2" } == json.value # => GET myjson
|
26
|
+
```
|
27
|
+
|
28
|
+
There are data structures for counters, enums, flags, lists, unique lists, sets, and slots:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
list = Kredis.list "mylist"
|
32
|
+
list << "hello world!"
|
33
|
+
[ "hello world!" ] == list.elements
|
34
|
+
|
35
|
+
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
|
39
|
+
|
40
|
+
unique_list = Kredis.unique_list "myuniquelist"
|
41
|
+
unique_list.append(%w[ 2 3 4 ])
|
42
|
+
unique_list.prepend(%w[ 1 2 3 4 ])
|
43
|
+
unique_list.append([])
|
44
|
+
unique_list << "5"
|
45
|
+
unique_list.remove(3)
|
46
|
+
[ "1", "2", "4", "5" ] == unique_list.elements
|
47
|
+
|
48
|
+
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
|
53
|
+
|
54
|
+
head_count = Kredis.counter "headcount"
|
55
|
+
0 == head_count.value # => GET "headcount"
|
56
|
+
head_count.increment
|
57
|
+
head_count.increment
|
58
|
+
head_count.decrement
|
59
|
+
1 == head_count.value # => GET "headcount"
|
60
|
+
|
61
|
+
counter = Kredis.counter "mycounter", expires_in: 5.seconds
|
62
|
+
counter.increment by: 2 # => SETEX "mycounter" 900 0 + INCR "mycounter" 2
|
63
|
+
2 == counter.value # => GET "mycounter"
|
64
|
+
sleep 6.seconds
|
65
|
+
0 == counter.value # => GET "mycounter"
|
66
|
+
|
67
|
+
enum = Kredis.enum "myenum", values: %w[ one two three ], default: "one"
|
68
|
+
"one" == enum.value
|
69
|
+
true == enum.one?
|
70
|
+
enum.value = "two"
|
71
|
+
"two" == enum.value
|
72
|
+
enum.value = "four"
|
73
|
+
"two" == enum.value
|
74
|
+
enum.reset
|
75
|
+
"one" == enum.value
|
76
|
+
|
77
|
+
slots = Kredis.slots "myslots", available: 3
|
78
|
+
true == slots.available?
|
79
|
+
slots.reserve
|
80
|
+
true == slots.available?
|
81
|
+
slots.reserve
|
82
|
+
true == slots.available?
|
83
|
+
slots.reserve
|
84
|
+
true == slots.available?
|
85
|
+
slots.reserve
|
86
|
+
false == slots.available?
|
87
|
+
slots.release
|
88
|
+
true == slots.available?
|
89
|
+
slots.reset
|
90
|
+
|
91
|
+
flag = Kredis.flag "myflag"
|
92
|
+
false == flag.marked?
|
93
|
+
flag.mark
|
94
|
+
true == flag.marked?
|
95
|
+
flag.remove
|
96
|
+
false == flag.marked?
|
97
|
+
|
98
|
+
flag.mark(expires_in: 1.second)
|
99
|
+
true == flag.marked?
|
100
|
+
sleep 0.5.seconds
|
101
|
+
true == flag.marked?
|
102
|
+
sleep 0.6.seconds
|
103
|
+
false == flag.marked?
|
104
|
+
```
|
105
|
+
|
106
|
+
And using structures on a different than the default `shared` redis instance, relying on `config/redis/secondary.yml`:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
one_string = Kredis.string "mystring"
|
110
|
+
two_string = Kredis.string "mystring", config: :secondary
|
111
|
+
|
112
|
+
one_string.value = "just on shared"
|
113
|
+
two_string.value != one_string.value
|
114
|
+
```
|
115
|
+
|
116
|
+
You can use all these structures in models:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class Person < ApplicationRecord
|
120
|
+
kredis_list :names
|
121
|
+
kredis_list :names_with_custom_key, key: ->(p) { "person:#{p.id}:names_customized" }
|
122
|
+
kredis_unique_list :skills, limit: 2
|
123
|
+
kredis_enum :morning, values: %w[ bright blue black ], default: "bright"
|
124
|
+
end
|
125
|
+
|
126
|
+
person = Person.find(5)
|
127
|
+
person.names.append "David", "Heinemeier", "Hansson" # => SADD person:5:names "David" "Heinemeier" "Hansson"
|
128
|
+
true == person.morning.bright?
|
129
|
+
person.morning.value = "blue"
|
130
|
+
true == person.morning.blue?
|
131
|
+
```
|
132
|
+
|
133
|
+
|
134
|
+
## Installation
|
135
|
+
|
136
|
+
1. Add the `kredis` gem to your Gemfile: `gem 'kredis'`
|
137
|
+
2. Run `./bin/bundle install`
|
138
|
+
3. Add a default configuration under `config/redis/shared.yml`
|
139
|
+
|
140
|
+
A default configuration can look like this for `config/redis/shared.yml`:
|
141
|
+
|
142
|
+
```yaml
|
143
|
+
production: &production
|
144
|
+
host: <%= ENV.fetch("REDIS_SHARED_HOST", "127.0.0.1") %>
|
145
|
+
port: <%= ENV.fetch("REDIS_SHARED_PORT", "6379") %>
|
146
|
+
timeout: 1
|
147
|
+
|
148
|
+
development: &development
|
149
|
+
host: <%= ENV.fetch("REDIS_SHARED_HOST", "127.0.0.1") %>
|
150
|
+
port: <%= ENV.fetch("REDIS_SHARED_PORT", "6379") %>
|
151
|
+
timeout: 1
|
152
|
+
|
153
|
+
test:
|
154
|
+
<<: *development
|
155
|
+
```
|
156
|
+
|
157
|
+
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`.
|
7
158
|
|
8
|
-
...
|
9
159
|
|
10
160
|
## License
|
11
161
|
|
data/lib/kredis.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
require "kredis/railtie"
|
2
|
+
require "kredis/version"
|
2
3
|
|
3
4
|
require "kredis/connections"
|
5
|
+
require "kredis/namespace"
|
4
6
|
require "kredis/types"
|
5
7
|
require "kredis/attributes"
|
6
|
-
require "kredis/
|
8
|
+
require "kredis/type_casting"
|
7
9
|
|
8
10
|
module Kredis
|
9
|
-
include Connections
|
10
|
-
include Namespace
|
11
|
-
include Types
|
11
|
+
include Connections, Namespace, Types, TypeCasting
|
12
12
|
|
13
13
|
extend self
|
14
|
+
|
15
|
+
mattr_accessor :logger
|
16
|
+
|
17
|
+
def redis(config: :shared)
|
18
|
+
configured_for(config)
|
19
|
+
end
|
14
20
|
end
|
data/lib/kredis/attributes.rb
CHANGED
@@ -2,33 +2,86 @@ module Kredis::Attributes
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
class_methods do
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
def kredis_proxy(name, key: nil, config: :shared)
|
6
|
+
kredis_connection_with __method__, name, key, config: config
|
7
|
+
end
|
8
|
+
|
9
|
+
def kredis_string(name, key: nil, config: :shared)
|
10
|
+
kredis_connection_with __method__, name, key, config: config
|
11
|
+
end
|
12
|
+
|
13
|
+
def kredis_integer(name, key: nil, config: :shared)
|
14
|
+
kredis_connection_with __method__, name, key, config: config
|
15
|
+
end
|
16
|
+
|
17
|
+
def kredis_datetime(name, key: nil, config: :shared)
|
18
|
+
kredis_connection_with __method__, name, key, config: config
|
19
|
+
end
|
20
|
+
|
21
|
+
def kredis_flag(name, key: nil, config: :shared)
|
22
|
+
kredis_connection_with __method__, name, key, config: config
|
23
|
+
|
24
|
+
define_method("#{name}?") do
|
25
|
+
send(name).marked?
|
14
26
|
end
|
15
27
|
end
|
16
28
|
|
17
|
-
def
|
18
|
-
|
29
|
+
def kredis_enum(name, key: nil, values:, default:, config: :shared)
|
30
|
+
kredis_connection_with __method__, name, key, values: values, default: default, config: config
|
31
|
+
end
|
32
|
+
|
33
|
+
def kredis_json(name, key: nil, config: :shared)
|
34
|
+
kredis_connection_with __method__, name, key, config: config
|
35
|
+
end
|
36
|
+
|
37
|
+
def kredis_list(name, key: nil, typed: :string, config: :shared)
|
38
|
+
kredis_connection_with __method__, name, key, typed: typed, config: config
|
39
|
+
end
|
40
|
+
|
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
|
43
|
+
end
|
44
|
+
|
45
|
+
def kredis_set(name, key: nil, typed: :string, config: :shared)
|
46
|
+
kredis_connection_with __method__, name, key, typed: typed, config: config
|
47
|
+
end
|
48
|
+
|
49
|
+
def kredis_slot(name, key: nil, config: :shared)
|
50
|
+
kredis_connection_with __method__, name, key, config: config
|
51
|
+
end
|
52
|
+
|
53
|
+
def kredis_slots(name, available:, key: nil, config: :shared)
|
54
|
+
kredis_connection_with __method__, name, key, available: available, config: config
|
55
|
+
end
|
19
56
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
57
|
+
private
|
58
|
+
def kredis_connection_with(method, name, key, **options)
|
59
|
+
ivar_symbol = :"@#{name}_#{method}"
|
60
|
+
type = method.to_s.sub("kredis_", "")
|
61
|
+
|
62
|
+
define_method(name) do
|
63
|
+
if instance_variable_defined?(ivar_symbol)
|
64
|
+
instance_variable_get(ivar_symbol)
|
65
|
+
else
|
66
|
+
instance_variable_set(ivar_symbol, Kredis.send(type, kredis_key_evaluated(key) || kredis_key_for_attribute(name), **options))
|
67
|
+
end
|
25
68
|
end
|
26
69
|
end
|
27
|
-
end
|
28
70
|
end
|
29
71
|
|
30
72
|
private
|
73
|
+
def kredis_key_evaluated(key)
|
74
|
+
case key
|
75
|
+
when String then key
|
76
|
+
when Proc then key.call(self)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
31
80
|
def kredis_key_for_attribute(name)
|
32
|
-
"#{self.class.name.tableize.gsub("/", ":")}:#{
|
81
|
+
"#{self.class.name.tableize.gsub("/", ":")}:#{extract_kredis_id}:#{name}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def extract_kredis_id
|
85
|
+
try(:id) or raise NotImplementedError, "kredis needs a unique id, either implement an id method or pass a custom key."
|
33
86
|
end
|
34
87
|
end
|
data/lib/kredis/connections.rb
CHANGED
@@ -5,10 +5,14 @@ module Kredis::Connections
|
|
5
5
|
mattr_accessor :configurator
|
6
6
|
|
7
7
|
def configured_for(name)
|
8
|
-
connections[name] ||=
|
8
|
+
connections[name] ||= begin
|
9
|
+
logger&.info "[Kredis] Connected to #{name}"
|
10
|
+
Redis.new configurator.config_for("redis/#{name}")
|
11
|
+
end
|
9
12
|
end
|
10
13
|
|
11
14
|
def clear_all
|
15
|
+
logger&.info "[Kredis] Connections all cleared"
|
12
16
|
connections.each_value(&:flushdb)
|
13
17
|
end
|
14
18
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "active_support/core_ext/module/delegation"
|
2
|
+
|
3
|
+
class Kredis::Migration
|
4
|
+
singleton_class.delegate :migrate_all, :migrate, :delete_all, to: :new
|
5
|
+
|
6
|
+
def initialize(config = :shared)
|
7
|
+
@redis = Kredis.configured_for config
|
8
|
+
@copy_sha = @redis.script "load", "redis.call('SETNX', KEYS[2], redis.call('GET', KEYS[1])); return 1;"
|
9
|
+
end
|
10
|
+
|
11
|
+
def migrate_all(key_pattern)
|
12
|
+
each_key_batch_matching(key_pattern) do |keys|
|
13
|
+
keys.each do |key|
|
14
|
+
ids = key.scan(/\d+/).map(&:to_i)
|
15
|
+
migrate from: key, to: yield(key, *ids)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def migrate(from:, to:)
|
21
|
+
namespaced_to = Kredis.namespaced_key(to)
|
22
|
+
|
23
|
+
if to.present? && from != namespaced_to
|
24
|
+
log_migration "Migrating key #{from} to #{namespaced_to}"
|
25
|
+
@redis.evalsha @copy_sha, keys: [ from, namespaced_to ]
|
26
|
+
else
|
27
|
+
log_migration "Skipping blank/unaltered migration key #{from} → #{to}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_all(key_pattern)
|
32
|
+
each_key_batch_matching(key_pattern) do |keys|
|
33
|
+
@redis.del *keys
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
SCAN_BATCH_SIZE = 1_000
|
39
|
+
|
40
|
+
def each_key_batch_matching(key_pattern, &block)
|
41
|
+
cursor = "0"
|
42
|
+
begin
|
43
|
+
cursor, keys = @redis.scan(cursor, match: key_pattern, count: SCAN_BATCH_SIZE)
|
44
|
+
@redis.pipelined { yield keys }
|
45
|
+
end until cursor == "0"
|
46
|
+
end
|
47
|
+
|
48
|
+
def log_migration(message)
|
49
|
+
Kredis.logger&.debug "[Kredis Migration] #{message}"
|
50
|
+
end
|
51
|
+
end
|
data/lib/kredis/railtie.rb
CHANGED
@@ -3,26 +3,30 @@ require "rails/railtie"
|
|
3
3
|
module Kredis
|
4
4
|
class Railtie < ::Rails::Railtie
|
5
5
|
config.kredis = ActiveSupport::OrderedOptions.new
|
6
|
-
config.eager_load_namespaces << Kredis
|
7
6
|
|
8
7
|
initializer "kredis.testing" do
|
9
8
|
ActiveSupport.on_load(:active_support_test_case) do
|
10
|
-
parallelize_setup
|
11
|
-
|
9
|
+
parallelize_setup { |worker| Kredis.namespace = "test-#{worker}" }
|
10
|
+
teardown { Kredis.clear_all }
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
14
|
+
initializer "kredis.logger" do
|
15
|
+
Kredis.logger = config.kredis.logger || Rails.logger
|
16
|
+
end
|
17
|
+
|
15
18
|
initializer "kredis.configurator" do
|
16
19
|
Kredis.configurator = Rails.application
|
17
20
|
end
|
18
21
|
|
19
22
|
initializer "kredis.attributes" do
|
20
|
-
|
21
|
-
|
23
|
+
# No load hook for Active Model, just defer until after initialization.
|
24
|
+
config.after_initialize do
|
25
|
+
ActiveModel::Model.include Kredis::Attributes if defined?(ActiveModel::Model)
|
22
26
|
end
|
23
27
|
|
24
28
|
ActiveSupport.on_load(:active_record) do
|
25
|
-
|
29
|
+
include Kredis::Attributes
|
26
30
|
end
|
27
31
|
end
|
28
32
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Kredis::TypeCasting
|
2
|
+
class InvalidType < StandardError; end
|
3
|
+
|
4
|
+
VALID_TYPES = %i[ string integer decimal float boolean datetime json ]
|
5
|
+
|
6
|
+
def type_to_string(value)
|
7
|
+
case value
|
8
|
+
when nil
|
9
|
+
""
|
10
|
+
when Integer
|
11
|
+
value.to_s
|
12
|
+
when BigDecimal
|
13
|
+
value.to_d
|
14
|
+
when Float
|
15
|
+
value.to_s
|
16
|
+
when TrueClass, FalseClass
|
17
|
+
value ? "t" : "f"
|
18
|
+
when Time, DateTime, ActiveSupport::TimeWithZone
|
19
|
+
value.iso8601(9)
|
20
|
+
when Hash
|
21
|
+
JSON.dump(value)
|
22
|
+
else
|
23
|
+
value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def string_to_type(value, type)
|
28
|
+
raise InvalidType if type && !type.in?(VALID_TYPES)
|
29
|
+
|
30
|
+
case type
|
31
|
+
when nil, :string then value
|
32
|
+
when :integer then value.to_i
|
33
|
+
when :decimal then value.to_d
|
34
|
+
when :float then value.to_f
|
35
|
+
when :boolean then value == "t" ? true : false
|
36
|
+
when :datetime then Time.iso8601(value)
|
37
|
+
when :json then JSON.load(value)
|
38
|
+
end if value.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
def types_to_strings(values)
|
42
|
+
Array(values).flatten.map { |value| type_to_string(value) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def strings_to_types(values, type)
|
46
|
+
Array(values).flatten.map { |value| string_to_type(value, type) }
|
47
|
+
end
|
48
|
+
end
|
data/lib/kredis/types.rb
CHANGED
@@ -1,27 +1,83 @@
|
|
1
1
|
module Kredis::Types
|
2
|
-
def
|
3
|
-
|
2
|
+
def proxy(key, config: :shared)
|
3
|
+
Proxy.new configured_for(config), namespaced_key(key)
|
4
4
|
end
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
|
7
|
+
def scalar(key, typed: :string, default: nil, config: :shared)
|
8
|
+
Scalar.new configured_for(config), namespaced_key(key), typed: typed, default: default
|
9
|
+
end
|
10
|
+
|
11
|
+
def string(key, default: nil, config: :shared)
|
12
|
+
Scalar.new configured_for(config), namespaced_key(key), typed: :string, default: default
|
13
|
+
end
|
14
|
+
|
15
|
+
def integer(key, default: nil, config: :shared)
|
16
|
+
Scalar.new configured_for(config), namespaced_key(key), typed: :integer, default: default
|
17
|
+
end
|
18
|
+
|
19
|
+
def decimal(key, default: nil, config: :shared)
|
20
|
+
Scalar.new configured_for(config), namespaced_key(key), typed: :decimal, default: default
|
8
21
|
end
|
9
22
|
|
10
|
-
def
|
11
|
-
|
23
|
+
def float(key, default: nil, config: :shared)
|
24
|
+
Scalar.new configured_for(config), namespaced_key(key), typed: :float, default: default
|
12
25
|
end
|
13
26
|
|
27
|
+
def boolean(key, default: nil, config: :shared)
|
28
|
+
Scalar.new configured_for(config), namespaced_key(key), typed: :boolean, default: default
|
29
|
+
end
|
30
|
+
|
31
|
+
def datetime(key, default: nil, config: :shared)
|
32
|
+
Scalar.new configured_for(config), namespaced_key(key), typed: :datetime, default: default
|
33
|
+
end
|
34
|
+
|
35
|
+
def json(key, default: nil, config: :shared)
|
36
|
+
Scalar.new configured_for(config), namespaced_key(key), typed: :json, default: default
|
37
|
+
end
|
38
|
+
|
39
|
+
|
14
40
|
def counter(key, expires_in: nil, config: :shared)
|
15
41
|
Counter.new configured_for(config), namespaced_key(key), expires_in: expires_in
|
16
42
|
end
|
17
43
|
|
18
|
-
def
|
19
|
-
|
44
|
+
def flag(key, config: :shared)
|
45
|
+
Flag.new configured_for(config), namespaced_key(key)
|
46
|
+
end
|
47
|
+
|
48
|
+
def enum(key, values:, default:, config: :shared)
|
49
|
+
Enum.new configured_for(config), namespaced_key(key), values: values, default: default
|
50
|
+
end
|
51
|
+
|
52
|
+
def list(key, typed: :string, config: :shared)
|
53
|
+
List.new configured_for(config), namespaced_key(key), typed: typed
|
54
|
+
end
|
55
|
+
|
56
|
+
def unique_list(key, typed: :string, limit: nil, config: :shared)
|
57
|
+
UniqueList.new configured_for(config), namespaced_key(key), typed: typed, limit: limit
|
58
|
+
end
|
59
|
+
|
60
|
+
def set(key, typed: :string, config: :shared)
|
61
|
+
Set.new configured_for(config), namespaced_key(key), typed: typed
|
62
|
+
end
|
63
|
+
|
64
|
+
def slot(key, config: :shared)
|
65
|
+
Slots.new configured_for(config), namespaced_key(key), available: 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def slots(key, available:, config: :shared)
|
69
|
+
Slots.new configured_for(config), namespaced_key(key), available: available
|
20
70
|
end
|
21
71
|
end
|
22
72
|
|
23
|
-
require "kredis/proxy"
|
73
|
+
require "kredis/types/proxy"
|
74
|
+
require "kredis/types/proxying"
|
75
|
+
|
76
|
+
require "kredis/types/scalar"
|
77
|
+
require "kredis/types/counter"
|
78
|
+
require "kredis/types/flag"
|
79
|
+
require "kredis/types/enum"
|
24
80
|
require "kredis/types/list"
|
25
81
|
require "kredis/types/unique_list"
|
26
|
-
require "kredis/types/
|
27
|
-
require "kredis/types/
|
82
|
+
require "kredis/types/set"
|
83
|
+
require "kredis/types/slots"
|
data/lib/kredis/types/counter.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
|
-
class Kredis::Types::Counter < Kredis::
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
end
|
1
|
+
class Kredis::Types::Counter < Kredis::Types::Proxying
|
2
|
+
proxying :multi, :set, :incrby, :decrby, :get
|
3
|
+
|
4
|
+
attr_accessor :expires_in
|
6
5
|
|
7
6
|
def increment(by: 1)
|
8
7
|
multi do
|
9
|
-
set 0, ex:
|
8
|
+
set 0, ex: expires_in, nx: true
|
10
9
|
incrby by
|
11
10
|
end
|
12
11
|
end
|
13
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
|
+
|
14
20
|
def value
|
15
21
|
get.to_i
|
16
22
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Kredis::Types::Enum < Kredis::Types::Proxying
|
2
|
+
proxying :set, :get, :del
|
3
|
+
|
4
|
+
attr_accessor :values, :default
|
5
|
+
|
6
|
+
def initialize(...)
|
7
|
+
super
|
8
|
+
define_predicates_for_values
|
9
|
+
end
|
10
|
+
|
11
|
+
def value=(value)
|
12
|
+
if validated_choice = value.presence_in(values)
|
13
|
+
set validated_choice
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def value
|
18
|
+
get || default
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset
|
22
|
+
del
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def define_predicates_for_values
|
27
|
+
values.each do |defined_value|
|
28
|
+
define_singleton_method("#{defined_value}?") { value == defined_value }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/kredis/types/list.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
|
-
class Kredis::Types::List < Kredis::
|
1
|
+
class Kredis::Types::List < Kredis::Types::Proxying
|
2
|
+
proxying :lrange, :lrem, :lpush, :rpush
|
3
|
+
|
4
|
+
attr_accessor :typed
|
5
|
+
|
2
6
|
def elements
|
3
|
-
lrange(0, -1) || []
|
7
|
+
strings_to_types(lrange(0, -1) || [], typed)
|
4
8
|
end
|
9
|
+
alias to_a elements
|
5
10
|
|
6
|
-
def remove(elements)
|
7
|
-
|
11
|
+
def remove(*elements)
|
12
|
+
types_to_strings(elements).each { |element| lrem 0, element }
|
8
13
|
end
|
9
14
|
|
10
|
-
def prepend(elements)
|
11
|
-
lpush elements
|
15
|
+
def prepend(*elements)
|
16
|
+
lpush types_to_strings(elements) if elements.flatten.any?
|
12
17
|
end
|
13
18
|
|
14
|
-
def append(elements)
|
15
|
-
rpush elements
|
19
|
+
def append(*elements)
|
20
|
+
rpush types_to_strings(elements) if elements.flatten.any?
|
16
21
|
end
|
22
|
+
alias << append
|
17
23
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Kredis::Types::Proxy
|
2
|
+
require_relative "proxy/failsafe"
|
3
|
+
include Failsafe
|
4
|
+
|
5
|
+
attr_accessor :redis, :key
|
6
|
+
|
7
|
+
def initialize(redis, key, **options)
|
8
|
+
@redis, @key = redis, key
|
9
|
+
options.each { |key, value| send("#{key}=", value) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def multi(...)
|
13
|
+
redis.multi(...)
|
14
|
+
end
|
15
|
+
|
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
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def log_message(method, *args, **kwargs)
|
25
|
+
args = args.flatten.reject(&:blank?).presence
|
26
|
+
kwargs = kwargs.reject { |_k, v| v.blank? }.presence
|
27
|
+
type_name = self.class.name.split("::").last
|
28
|
+
|
29
|
+
"[Kredis #{type_name}] #{method.upcase} #{key} #{args&.inspect} #{kwargs&.inspect}".chomp
|
30
|
+
end
|
31
|
+
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
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "active_support/core_ext/module/delegation"
|
2
|
+
|
3
|
+
class Kredis::Types::Proxying
|
4
|
+
attr_accessor :proxy, :redis, :key
|
5
|
+
|
6
|
+
def self.proxying(*commands)
|
7
|
+
delegate *commands, to: :proxy
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(redis, key, **options)
|
11
|
+
@redis, @key = redis, key
|
12
|
+
@proxy = Kredis::Types::Proxy.new(redis, key)
|
13
|
+
options.each { |key, value| send("#{key}=", value) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def failsafe(returning: nil, &block)
|
17
|
+
proxy.suppress_failsafe_with(returning: returning, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
delegate :type_to_string, :string_to_type, :types_to_strings, :strings_to_types, to: :Kredis
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Kredis::Types::Scalar < Kredis::Types::Proxying
|
2
|
+
proxying :set, :get, :exists?, :del
|
3
|
+
|
4
|
+
attr_accessor :typed, :default
|
5
|
+
|
6
|
+
def value=(value)
|
7
|
+
set type_to_string(value)
|
8
|
+
end
|
9
|
+
|
10
|
+
def value
|
11
|
+
string_to_type(get, typed) || default
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
get || default&.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def assigned?
|
19
|
+
exists?
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear
|
23
|
+
del
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Kredis::Types::Set < Kredis::Types::Proxying
|
2
|
+
proxying :smembers, :sadd, :srem, :multi, :del, :sismember, :scard, :spop
|
3
|
+
|
4
|
+
attr_accessor :typed
|
5
|
+
|
6
|
+
def members
|
7
|
+
strings_to_types(smembers || [], typed).sort
|
8
|
+
end
|
9
|
+
alias to_a members
|
10
|
+
|
11
|
+
def add(*members)
|
12
|
+
sadd types_to_strings(members) if members.flatten.any?
|
13
|
+
end
|
14
|
+
alias << add
|
15
|
+
|
16
|
+
def remove(*members)
|
17
|
+
srem types_to_strings(members) if members.flatten.any?
|
18
|
+
end
|
19
|
+
|
20
|
+
def replace(*members)
|
21
|
+
multi do
|
22
|
+
del
|
23
|
+
add members
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def include?(member)
|
28
|
+
sismember type_to_string(member)
|
29
|
+
end
|
30
|
+
|
31
|
+
def size
|
32
|
+
scard.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
def take
|
36
|
+
spop
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear
|
40
|
+
del
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Kredis::Types::Slots < Kredis::Types::Proxying
|
2
|
+
class NotAvailable < StandardError; end
|
3
|
+
|
4
|
+
proxying :incr, :decr, :get, :del
|
5
|
+
|
6
|
+
attr_accessor :available
|
7
|
+
|
8
|
+
def reserve
|
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
|
23
|
+
true
|
24
|
+
else
|
25
|
+
release
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def release
|
33
|
+
decr
|
34
|
+
end
|
35
|
+
|
36
|
+
def available?
|
37
|
+
failsafe returning: false do
|
38
|
+
get.to_i < available
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset
|
43
|
+
del
|
44
|
+
end
|
45
|
+
end
|
@@ -1,23 +1,23 @@
|
|
1
1
|
# You'd normally call this a set, but Redis already has another data type for that
|
2
2
|
class Kredis::Types::UniqueList < Kredis::Types::List
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
end
|
3
|
+
proxying :multi, :ltrim
|
4
|
+
|
5
|
+
attr_accessor :typed, :limit
|
7
6
|
|
8
7
|
def prepend(elements)
|
9
8
|
multi do
|
10
9
|
remove elements
|
11
10
|
super
|
12
|
-
ltrim 0, (
|
13
|
-
end
|
11
|
+
ltrim 0, (limit - 1) if limit
|
12
|
+
end if Array(elements).flatten.any?
|
14
13
|
end
|
15
14
|
|
16
15
|
def append(elements)
|
17
16
|
multi do
|
18
17
|
remove elements
|
19
18
|
super
|
20
|
-
ltrim (
|
21
|
-
end
|
19
|
+
ltrim (limit - 1), -1 if limit
|
20
|
+
end if Array(elements).flatten.any?
|
22
21
|
end
|
22
|
+
alias << append
|
23
23
|
end
|
data/lib/kredis/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kredis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
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-
|
12
|
+
date: 2021-02-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -31,15 +31,15 @@ dependencies:
|
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '4.
|
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.
|
42
|
-
description:
|
41
|
+
version: '4.2'
|
42
|
+
description:
|
43
43
|
email: david@hey.com
|
44
44
|
executables: []
|
45
45
|
extensions: []
|
@@ -50,20 +50,28 @@ files:
|
|
50
50
|
- lib/kredis.rb
|
51
51
|
- lib/kredis/attributes.rb
|
52
52
|
- lib/kredis/connections.rb
|
53
|
+
- lib/kredis/migration.rb
|
53
54
|
- lib/kredis/namespace.rb
|
54
|
-
- lib/kredis/proxy.rb
|
55
55
|
- lib/kredis/railtie.rb
|
56
|
+
- lib/kredis/type_casting.rb
|
56
57
|
- lib/kredis/types.rb
|
57
58
|
- lib/kredis/types/counter.rb
|
59
|
+
- lib/kredis/types/enum.rb
|
60
|
+
- lib/kredis/types/flag.rb
|
58
61
|
- lib/kredis/types/list.rb
|
59
|
-
- lib/kredis/types/
|
62
|
+
- lib/kredis/types/proxy.rb
|
63
|
+
- lib/kredis/types/proxy/failsafe.rb
|
64
|
+
- lib/kredis/types/proxying.rb
|
65
|
+
- lib/kredis/types/scalar.rb
|
66
|
+
- lib/kredis/types/set.rb
|
67
|
+
- lib/kredis/types/slots.rb
|
60
68
|
- lib/kredis/types/unique_list.rb
|
61
69
|
- lib/kredis/version.rb
|
62
70
|
homepage: https://github.com/rails/kredis
|
63
71
|
licenses:
|
64
72
|
- MIT
|
65
73
|
metadata: {}
|
66
|
-
post_install_message:
|
74
|
+
post_install_message:
|
67
75
|
rdoc_options: []
|
68
76
|
require_paths:
|
69
77
|
- lib
|
@@ -71,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
79
|
requirements:
|
72
80
|
- - ">="
|
73
81
|
- !ruby/object:Gem::Version
|
74
|
-
version: 2.
|
82
|
+
version: 2.7.0
|
75
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
84
|
requirements:
|
77
85
|
- - ">="
|
@@ -79,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
87
|
version: '0'
|
80
88
|
requirements: []
|
81
89
|
rubygems_version: 3.1.2
|
82
|
-
signing_key:
|
90
|
+
signing_key:
|
83
91
|
specification_version: 4
|
84
92
|
summary: Higher-level data structures built on Redis.
|
85
93
|
test_files: []
|
data/lib/kredis/proxy.rb
DELETED
data/lib/kredis/types/mutex.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
class Kredis::Types::Mutex < Kredis::Proxy
|
2
|
-
def initialize(redis, key, expires_in: nil)
|
3
|
-
@expires_in = expires_in
|
4
|
-
super redis, key
|
5
|
-
end
|
6
|
-
|
7
|
-
def lock
|
8
|
-
set 1, ex: @expires_in
|
9
|
-
end
|
10
|
-
|
11
|
-
def unlock
|
12
|
-
del
|
13
|
-
end
|
14
|
-
|
15
|
-
def locked?
|
16
|
-
get
|
17
|
-
end
|
18
|
-
|
19
|
-
def synchronize
|
20
|
-
lock
|
21
|
-
yield
|
22
|
-
ensure
|
23
|
-
unlock
|
24
|
-
end
|
25
|
-
end
|