kredis 0.1.2 → 0.2.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: 57c373219f474818bb85a7da4ef3a07bd4910cd685704b54549969c364ee5993
4
- data.tar.gz: f220ce448caa279514e249bc7c2f744c7276946d5f5af5a9899a9f6e14d3b5be
3
+ metadata.gz: 9aa90994f3d30a19d52df3fbc284d43d2637e940de06f221ecffc2853eeda459
4
+ data.tar.gz: 713137c0ec2c28bfb80df9c94b3e61c4b8f5906c9d68631fd5e26e4ac8ef0771
5
5
  SHA512:
6
- metadata.gz: ef637b80601d21e815317201ab426da35a56926a2d069303cf1244e5a51f4ab0f80f7ef6e47a566d306248bbc2006c8a0dcdfd6990e0abf3057b5474ba7b6fc0
7
- data.tar.gz: 99e4587242b125cfc78deff59e410055f9fefbfce726942ce1192e6065a6e08c1cfd0d83d409221c54418803b4405643ce8f873de8fbb0ef0c781ba882625c0e
6
+ metadata.gz: afcbb020c7e7c412090a7798c433882adf7d54e293728eff3a7d39b655d41531a3bae5f307ea10faa72a39e6ed1cb1f7db1f3f6535351299d26f65fd20e728fb
7
+ data.tar.gz: 7de324aa868056600c28d82911ef654c7d9f58c5d85823ab1271a7518c76f9dbb31a2be23082cb9ab2bf210401eac0ceca698412a38f01779af310bcf5893624
data/README.md CHANGED
@@ -1,11 +1,94 @@
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
- ## Development
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.string "myinteger"
20
+ integer.value = 5 # => SET myinteger "5"
21
+ 5 == string.value # => GET myinteger
22
+
23
+ json = Kredis.json "myjson"
24
+ integer.value = { "one" => 1, "two" => "2" } # => SET myjson "{\"one\":1,\"two\":\"2\"}"
25
+ { "one" => 1, "two" => "2" } == string.value # => GET myjson
26
+ ```
27
+
28
+ There are data structures for counters, enums, flags, lists, uniqe lists, sets, and slots:
29
+
30
+ ```ruby
31
+ list = Kredis.list "mylist", typed: :integer
32
+ list.append([ 1, 2, 3 ]) # => LPUSH mylist "1" "2" "3"
33
+ list << 4 # => LPUSH mylist "4"
34
+ [ 1, 2, 3, 4 ] == list.elements # LRANGE 0 -1
35
+
36
+ set = Kredis.set "myset", typed: :datetime
37
+ set.add(DateTime.tomorrow, DateTime.yesterday) # => SADD myset "2021-02-03 00:00:00 +0100" "2021-02-01 00:00:00 +0100"
38
+ set << DateTime.tomorrow # => SADD myset "2021-02-03 00:00:00 +0100"
39
+ 2 == set.size # => SCARD myset
40
+ [ DateTime.tomorrow, DateTime.yesterday ] == set.elements # => SMEMBERS myset
41
+
42
+ counter = Kredis.counter "mycounter", expires_in: 15.minutes
43
+ counter.increment by: 2 # => SETEX "mycounter" 900 0 + INCR "mycounter" 2
44
+ 2 == counter.value # => GET "mycounter"
45
+ travel 16.minutes
46
+ 0 == counter.value # => GET "mycounter"
47
+ ```
48
+
49
+ You can use all these structures in models:
50
+
51
+ ```ruby
52
+ class Person < ApplicationRecord
53
+ kredis_list :names
54
+ kredis_list :names_with_custom_key, key: ->(p) { "person:#{p.id}:names_customized" }
55
+ kredis_unique_list :skills, limit: 2
56
+ kredis_enum :morning, values: %w[ bright blue black ], default: "bright"
57
+ end
58
+
59
+ person = Person.find(5)
60
+ person.names.append "David", "Heinemeier", "Hansson" # => SADD person:5:names "David" "Heinemeier" "Hansson"
61
+ true == person.morning.bright?
62
+ person.morning.value = "blue"
63
+ true == person.morning.blue?
64
+ ```
65
+
66
+
67
+ ## Installation
68
+
69
+ 1. Add the `kredis` gem to your Gemfile: `gem 'kredis'`
70
+ 2. Run `./bin/bundle install`
71
+ 3. Add a default configuration under `config/redis/shared.yml`
72
+
73
+ A default configuration can look like this for `config/redis/shared.yml`:
74
+
75
+ ```yaml
76
+ production: &production
77
+ host: <%= ENV.fetch("REDIS_SHARED_HOST", "127.0.0.1") %>
78
+ port: <%= ENV.fetch("REDIS_SHARED_PORT", "6379") %>
79
+ timeout: 1
80
+
81
+ development: &development
82
+ host: <%= ENV.fetch("REDIS_SHARED_HOST", "127.0.0.1") %>
83
+ port: <%= ENV.fetch("REDIS_SHARED_PORT", "6379") %>
84
+ timeout: 1
85
+
86
+ test:
87
+ <<: *development
88
+ ```
89
+
90
+ Additional configurations can be added under `config/redis/*.yml` and referenced when a type is created.
7
91
 
8
- ...
9
92
 
10
93
  ## License
11
94
 
data/lib/kredis.rb CHANGED
@@ -1,19 +1,19 @@
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/namespace"
7
- require "kredis/logger"
8
+ require "kredis/type_casting"
8
9
 
9
10
  module Kredis
10
- include Connections
11
- include Namespace
12
- include Types
13
- include Logger
11
+ include Connections, Namespace, Types, TypeCasting
14
12
 
15
13
  extend self
16
14
 
15
+ mattr_accessor :logger
16
+
17
17
  def redis(config: :shared)
18
18
  configured_for(config)
19
19
  end
@@ -3,80 +3,70 @@ module Kredis::Attributes
3
3
 
4
4
  class_methods do
5
5
  def kredis_proxy(name, key: nil, config: :shared)
6
- ivar_symbol = :"@#{name}_kredis_proxy"
6
+ kredis_connection_with __method__, name, key, config: config
7
+ end
7
8
 
8
- define_method(name) do
9
- if instance_variable_defined?(ivar_symbol)
10
- instance_variable_get(ivar_symbol)
11
- else
12
- instance_variable_set(ivar_symbol, Kredis.proxy(kredis_key_evaluated(key) || kredis_key_for_attribute(name), config: config))
13
- end
14
- end
9
+ def kredis_string(name, key: nil, config: :shared)
10
+ kredis_connection_with __method__, name, key, config: config
15
11
  end
16
12
 
17
- def kredis_list(name, key: nil, config: :shared)
18
- ivar_symbol = :"@#{name}_kredis_list"
13
+ def kredis_integer(name, key: nil, config: :shared)
14
+ kredis_connection_with __method__, name, key, config: config
15
+ end
19
16
 
20
- define_method(name) do
21
- if instance_variable_defined?(ivar_symbol)
22
- instance_variable_get(ivar_symbol)
23
- else
24
- instance_variable_set(ivar_symbol, Kredis.list(kredis_key_evaluated(key) || kredis_key_for_attribute(name), config: config))
25
- end
26
- end
17
+ def kredis_datetime(name, key: nil, config: :shared)
18
+ kredis_connection_with __method__, name, key, config: config
27
19
  end
28
20
 
29
- def kredis_unique_list(name, limit: nil, key: nil, config: :shared)
30
- ivar_symbol = :"@#{name}_kredis_unique_list"
21
+ def kredis_flag(name, key: nil, config: :shared)
22
+ kredis_connection_with __method__, name, key, config: config
31
23
 
32
- define_method(name) do
33
- if instance_variable_defined?(ivar_symbol)
34
- instance_variable_get(ivar_symbol)
35
- else
36
- instance_variable_set(ivar_symbol, Kredis.unique_list(kredis_key_evaluated(key) || kredis_key_for_attribute(name), limit: limit, config: config))
37
- end
24
+ define_method("#{name}?") do
25
+ send(name).marked?
38
26
  end
39
27
  end
40
28
 
41
- def kredis_flag(name, key: nil, config: :shared)
42
- ivar_symbol = :"@#{name}_kredis_flag"
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
43
32
 
44
- define_method(name) do
45
- if instance_variable_defined?(ivar_symbol)
46
- instance_variable_get(ivar_symbol)
47
- else
48
- instance_variable_set(ivar_symbol, Kredis.flag(kredis_key_evaluated(key) || kredis_key_for_attribute(name), config: config))
49
- end
50
- end
33
+ def kredis_json(name, key: nil, config: :shared)
34
+ kredis_connection_with __method__, name, key, config: config
35
+ end
51
36
 
52
- define_method("#{name}?") do
53
- instance_variable_defined?(ivar_symbol) && instance_variable_get(ivar_symbol).marked?
54
- end
37
+ def kredis_list(name, key: nil, typed: :string, config: :shared)
38
+ kredis_connection_with __method__, name, key, typed: typed, config: config
55
39
  end
56
40
 
57
- def kredis_string(name, key: nil, config: :shared)
58
- ivar_symbol = :"@#{name}_kredis_string"
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
59
44
 
60
- define_method(name) do
61
- if instance_variable_defined?(ivar_symbol)
62
- instance_variable_get(ivar_symbol)
63
- else
64
- instance_variable_set(ivar_symbol, Kredis.string(kredis_key_evaluated(key) || kredis_key_for_attribute(name), config: config))
65
- end
66
- end
45
+ def kredis_set(name, key: nil, typed: :string, config: :shared)
46
+ kredis_connection_with __method__, name, key, typed: typed, config: config
67
47
  end
68
48
 
69
- def kredis_integer(name, key: nil, config: :shared)
70
- ivar_symbol = :"@#{name}_kredis_integer"
49
+ def kredis_slot(name, key: nil, config: :shared)
50
+ kredis_connection_with __method__, name, key, config: config
51
+ end
71
52
 
72
- define_method(name) do
73
- if instance_variable_defined?(ivar_symbol)
74
- instance_variable_get(ivar_symbol)
75
- else
76
- instance_variable_set(ivar_symbol, Kredis.integer(kredis_key_evaluated(key) || kredis_key_for_attribute(name), config: config))
53
+ def kredis_slots(name, available:, key: nil, config: :shared)
54
+ kredis_connection_with __method__, name, key, available: available, config: config
55
+ end
56
+
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
77
68
  end
78
69
  end
79
- end
80
70
  end
81
71
 
82
72
  private
@@ -87,7 +77,7 @@ module Kredis::Attributes
87
77
  end
88
78
  end
89
79
 
90
- def kredis_key_for_attribute(name, key: nil)
80
+ def kredis_key_for_attribute(name)
91
81
  "#{self.class.name.tableize.gsub("/", ":")}:#{id}:#{name}"
92
82
  end
93
83
  end
@@ -5,10 +5,14 @@ module Kredis::Connections
5
5
  mattr_accessor :configurator
6
6
 
7
7
  def configured_for(name)
8
- connections[name] ||= Redis.new configurator.config_for("redis/#{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,47 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ class Kredis::Migration
4
+ singleton_class.delegate :migrate_all, :migrate, 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
+ find_keys_in_batches_matching(key_pattern) do |keys|
13
+ @redis.multi do
14
+ keys.each do |key|
15
+ ids = key.scan(/\d+/).map(&:to_i)
16
+ migrate from: key, to: yield(key, *ids)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def migrate(from:, to:)
23
+ to = Kredis.namespaced_key(to)
24
+
25
+ if from != to
26
+ log_migration "Migrating key #{from} to #{to}"
27
+ @redis.evalsha @copy_sha, keys: [ from, to ]
28
+ else
29
+ log_migration "Skipping unaltered migration key #{from}"
30
+ end
31
+ end
32
+
33
+ private
34
+ SCAN_BATCH_SIZE = 1_000
35
+
36
+ def find_keys_in_batches_matching(key_pattern, &block)
37
+ cursor = "0"
38
+ begin
39
+ cursor, keys = @redis.scan(cursor, match: key_pattern, count: SCAN_BATCH_SIZE)
40
+ yield keys
41
+ end until cursor == "0"
42
+ end
43
+
44
+ def log_migration(message)
45
+ Kredis.logger&.debug "[Kredis Migration] #{message}"
46
+ end
47
+ end
@@ -3,7 +3,6 @@ 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
@@ -13,7 +12,7 @@ module Kredis
13
12
  end
14
13
 
15
14
  initializer "kredis.logger" do
16
- Kredis.logger = Rails.logger
15
+ Kredis.logger = config.kredis.logger || Rails.logger
17
16
  end
18
17
 
19
18
  initializer "kredis.configurator" do
@@ -21,12 +20,13 @@ module Kredis
21
20
  end
22
21
 
23
22
  initializer "kredis.attributes" do
23
+ # TODO: Add run_load_hooks to ActiveModel::Model so this runs.
24
24
  ActiveSupport.on_load(:active_model) do
25
- ActiveModel::Base.send :include, Kredis::Attributes
25
+ include Kredis::Attributes
26
26
  end
27
27
 
28
28
  ActiveSupport.on_load(:active_record) do
29
- ActiveRecord::Base.send :include, Kredis::Attributes
29
+ include Kredis::Attributes
30
30
  end
31
31
  end
32
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.to_f
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.at(value.to_i)
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,16 +1,42 @@
1
1
  module Kredis::Types
2
2
  def proxy(key, config: :shared)
3
- Kredis::Types::Proxy.new configured_for(config), namespaced_key(key)
3
+ Proxy.new configured_for(config), namespaced_key(key)
4
4
  end
5
5
 
6
- def list(key, config: :shared)
7
- List.new configured_for(config), namespaced_key(key)
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
21
+ end
22
+
23
+ def float(key, default: nil, config: :shared)
24
+ Scalar.new configured_for(config), namespaced_key(key), typed: :float, default: default
25
+ end
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
8
33
  end
9
34
 
10
- def unique_list(key, limit: nil, config: :shared)
11
- UniqueList.new configured_for(config), namespaced_key(key), limit: limit
35
+ def json(key, default: nil, config: :shared)
36
+ Scalar.new configured_for(config), namespaced_key(key), typed: :json, default: default
12
37
  end
13
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
@@ -19,24 +45,39 @@ module Kredis::Types
19
45
  Flag.new configured_for(config), namespaced_key(key)
20
46
  end
21
47
 
22
- def string(key, config: :shared)
23
- Kredis::Types::String.new configured_for(config), namespaced_key(key)
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
24
62
  end
25
63
 
26
- def integer(key, config: :shared)
27
- Kredis::Types::Integer.new configured_for(config), namespaced_key(key)
64
+ def slot(key, config: :shared)
65
+ Slots.new configured_for(config), namespaced_key(key), available: 1
28
66
  end
29
67
 
30
- def mutex(key, expires_in: nil, config: :shared)
31
- Mutex.new configured_for(config), namespaced_key(key), expires_in: expires_in
68
+ def slots(key, available:, config: :shared)
69
+ Slots.new configured_for(config), namespaced_key(key), available: available
32
70
  end
33
71
  end
34
72
 
35
73
  require "kredis/types/proxy"
36
- require "kredis/types/list"
37
- require "kredis/types/unique_list"
74
+ require "kredis/types/proxying"
75
+
76
+ require "kredis/types/scalar"
38
77
  require "kredis/types/counter"
39
78
  require "kredis/types/flag"
40
- require "kredis/types/string"
41
- require "kredis/types/integer"
42
- require "kredis/types/mutex"
79
+ require "kredis/types/enum"
80
+ require "kredis/types/list"
81
+ require "kredis/types/unique_list"
82
+ require "kredis/types/set"
83
+ require "kredis/types/slots"
@@ -1,12 +1,11 @@
1
- class Kredis::Types::Counter < Kredis::Types::Proxy
2
- def initialize(redis, key, expires_in: nil)
3
- @expires_in = expires_in
4
- super redis, key
5
- end
1
+ class Kredis::Types::Counter < Kredis::Types::Proxying
2
+ proxying :multi, :set, :incrby, :get
3
+
4
+ attr_accessor :expires_in
6
5
 
7
6
  def increment(by: 1)
8
7
  multi do
9
- set 0, ex: @expires_in, nx: true
8
+ set 0, ex: expires_in, nx: true
10
9
  incrby by
11
10
  end
12
11
  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
@@ -1,6 +1,8 @@
1
- class Kredis::Types::Flag < Kredis::Types::Proxy
2
- def mark
3
- set 1
1
+ class Kredis::Types::Flag < Kredis::Types::Proxying
2
+ proxying :set, :exists?, :del
3
+
4
+ def mark(expires_in: nil)
5
+ set 1, ex: expires_in
4
6
  end
5
7
 
6
8
  def marked?
@@ -1,17 +1,23 @@
1
- class Kredis::Types::List < Kredis::Types::Proxy
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
- Array(elements).each { |element| lrem 0, element }
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 if Array(elements).any?
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 if Array(elements).any?
19
+ def append(*elements)
20
+ rpush types_to_strings(elements) if elements.flatten.any?
16
21
  end
22
+ alias << append
17
23
  end
@@ -1,14 +1,26 @@
1
1
  class Kredis::Types::Proxy
2
- def initialize(redis, key)
2
+ attr_accessor :redis, :key
3
+
4
+ def initialize(redis, key, **options)
3
5
  @redis, @key = redis, key
6
+ options.each { |key, value| send("#{key}=", value) }
4
7
  end
5
8
 
6
9
  def multi(...)
7
- @redis.multi(...)
10
+ redis.multi(...)
8
11
  end
9
12
 
10
13
  def method_missing(method, *args, **kwargs)
11
- Kredis.logger&.debug "[Kredis] #{method} #{@key} #{args.inspect if args.compact_blank.any?} #{kwargs.inspect if kwargs.compact_blank.any?}".chomp
12
- @redis.public_send method, @key, *args, **kwargs
14
+ Kredis.logger&.debug log_message(method, *args, **kwargs)
15
+ redis.public_send method, key, *args, **kwargs
13
16
  end
17
+
18
+ private
19
+ def log_message(method, *args, **kwargs)
20
+ args = args.flatten.compact_blank.presence
21
+ kwargs = kwargs.compact_blank.presence
22
+ type_name = self.class.name.split("::").last
23
+
24
+ "[Kredis #{type_name}] #{method.upcase} #{key} #{args&.inspect} #{kwargs&.inspect}".chomp
25
+ end
14
26
  end
@@ -0,0 +1,18 @@
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
+ private
17
+ delegate :type_to_string, :string_to_type, :types_to_strings, :strings_to_types, to: :Kredis
18
+ 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
33
+ end
34
+
35
+ def take
36
+ spop
37
+ end
38
+
39
+ def clear
40
+ del
41
+ end
42
+ end
@@ -0,0 +1,41 @@
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
+ if block_given?
10
+ begin
11
+ if reserve
12
+ yield
13
+ true
14
+ else
15
+ false
16
+ end
17
+ ensure
18
+ release
19
+ end
20
+ else
21
+ if incr <= available
22
+ true
23
+ else
24
+ release
25
+ false
26
+ end
27
+ end
28
+ end
29
+
30
+ def release
31
+ decr
32
+ end
33
+
34
+ def available?
35
+ get.to_i < available
36
+ end
37
+
38
+ def reset
39
+ del
40
+ end
41
+ 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
- def initialize(redis, key, limit: nil)
4
- @limit = limit
5
- super redis, key
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, (@limit - 1) if @limit
13
- end if Array(elements).any?
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 (@limit - 1), -1 if @limit
21
- end if Array(elements).any?
19
+ ltrim (limit - 1), -1 if limit
20
+ end if Array(elements).flatten.any?
22
21
  end
22
+ alias << append
23
23
  end
@@ -1,3 +1,3 @@
1
1
  module Kredis
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kredis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.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: 2021-01-30 00:00:00.000000000 Z
12
+ date: 2021-02-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -50,17 +50,20 @@ files:
50
50
  - lib/kredis.rb
51
51
  - lib/kredis/attributes.rb
52
52
  - lib/kredis/connections.rb
53
- - lib/kredis/logger.rb
53
+ - lib/kredis/migration.rb
54
54
  - lib/kredis/namespace.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
58
60
  - lib/kredis/types/flag.rb
59
- - lib/kredis/types/integer.rb
60
61
  - lib/kredis/types/list.rb
61
- - lib/kredis/types/mutex.rb
62
62
  - lib/kredis/types/proxy.rb
63
- - lib/kredis/types/string.rb
63
+ - lib/kredis/types/proxying.rb
64
+ - lib/kredis/types/scalar.rb
65
+ - lib/kredis/types/set.rb
66
+ - lib/kredis/types/slots.rb
64
67
  - lib/kredis/types/unique_list.rb
65
68
  - lib/kredis/version.rb
66
69
  homepage: https://github.com/rails/kredis
data/lib/kredis/logger.rb DELETED
@@ -1,5 +0,0 @@
1
- require "redis"
2
-
3
- module Kredis::Logger
4
- mattr_accessor :logger
5
- end
@@ -1,9 +0,0 @@
1
- class Kredis::Types::Integer < Kredis::Types::String
2
- def value
3
- get&.to_i
4
- end
5
-
6
- def to_s
7
- value.to_s
8
- end
9
- end
@@ -1,25 +0,0 @@
1
- class Kredis::Types::Mutex < Kredis::Types::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
@@ -1,21 +0,0 @@
1
- class Kredis::Types::String < Kredis::Types::Proxy
2
- def assign=(value)
3
- set value
4
- end
5
-
6
- def assigned?
7
- exists?
8
- end
9
-
10
- def value
11
- get
12
- end
13
-
14
- def to_s
15
- value
16
- end
17
-
18
- def clear
19
- del
20
- end
21
- end