rediconn 0.1.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 +7 -0
- data/README.md +1 -0
- data/lib/rediconn/redis_connection.rb +141 -0
- data/lib/rediconn/redis_model.rb +67 -0
- data/lib/rediconn/redis_proxy.rb +51 -0
- data/lib/rediconn/redis_script.rb +161 -0
- data/lib/rediconn/version.rb +3 -0
- data/lib/rediconn.rb +8 -0
- data/rediconn.gemspec +30 -0
- data/spec/rediconn/redis_connection_spec.rb +38 -0
- data/spec/spec_helper.rb +12 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6ca73adca8e6a1b981b52e05f3ba3eedc3ec1e4043c564128ad47646d9153014
|
4
|
+
data.tar.gz: 8a8582b2e4a53e6719bc49176d43524359bf8bba278c81506edd65cb35cb0204
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 712cb5abbb5efb2eece4ede5624f3d2660bce499c654fa6b2cd8e9780293298f59a3e9e1d130237502c6fb74547f643e2c41dcd8754c6c347da5c57f182e0160
|
7
|
+
data.tar.gz: 906d3a5416e33d164b7705b05512ee43e1932245c493692e3ace4169d091d1a6115aa079e10202166399dc352fb9e0da5763ee363978dfb3db872edb92071f07
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# RediConn
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "connection_pool"
|
4
|
+
require "redis"
|
5
|
+
require "uri"
|
6
|
+
|
7
|
+
# Adapted from Sidekiq 6.x Implementation
|
8
|
+
|
9
|
+
module RediConn
|
10
|
+
class RedisConnection < ConnectionPool
|
11
|
+
KNOWN_REDIS_URL_ENVS = %w[REDIS_URL REDISTOGO_URL REDISCLOUD_URL].freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def configured?(prefix, explicit: true)
|
15
|
+
determine_redis_provider(prefix: prefix, explicit: explicit).present?
|
16
|
+
end
|
17
|
+
|
18
|
+
def shared_pools
|
19
|
+
@shared_pools ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(raw_options = {})
|
23
|
+
options = raw_options.transform_keys(&:to_sym)
|
24
|
+
|
25
|
+
if !options[:url] && (u = determine_redis_provider(prefix: raw_options[:env_prefix]))
|
26
|
+
options[:url] = u
|
27
|
+
end
|
28
|
+
|
29
|
+
options[:timeout] ||= 1
|
30
|
+
options[:size] ||= if options[:env_prefix] && (v = ENV["#{options[:env_prefix]}_REDIS_POOL_SIZE"])
|
31
|
+
Integer(v)
|
32
|
+
elsif ENV["RAILS_MAX_THREADS"]
|
33
|
+
Integer(ENV["RAILS_MAX_THREADS"])
|
34
|
+
else
|
35
|
+
5
|
36
|
+
end
|
37
|
+
|
38
|
+
if options[:dedicated]
|
39
|
+
initialize_pool(options)
|
40
|
+
else
|
41
|
+
url = options[:url]
|
42
|
+
cpool = shared_pools[url] ||= initialize_pool(options)
|
43
|
+
|
44
|
+
cpool.resize!(options[:size])
|
45
|
+
|
46
|
+
# TODO Would be nice to respect timeouts
|
47
|
+
|
48
|
+
cpool
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def client_opts(options)
|
55
|
+
opts = options.dup
|
56
|
+
if opts[:namespace]
|
57
|
+
opts.delete(:namespace)
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.delete(:size)
|
61
|
+
opts.delete(:env_prefix)
|
62
|
+
|
63
|
+
if opts[:network_timeout]
|
64
|
+
opts[:timeout] = opts[:network_timeout]
|
65
|
+
opts.delete(:network_timeout)
|
66
|
+
end
|
67
|
+
|
68
|
+
opts[:reconnect_attempts] ||= 1
|
69
|
+
|
70
|
+
opts
|
71
|
+
end
|
72
|
+
|
73
|
+
def determine_redis_provider(prefix: nil, explicit: false)
|
74
|
+
vars = []
|
75
|
+
|
76
|
+
if prefix.present?
|
77
|
+
if (ptr = ENV["#{prefix}_REDIS_PROVIDER"]).present?
|
78
|
+
return ENV[ptr]
|
79
|
+
else
|
80
|
+
vars.push(*KNOWN_REDIS_URL_ENVS.map { |e| ENV["#{prefix}_#{e}"] })
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
if !explicit || !prefix.present?
|
85
|
+
if (ptr = ENV["REDIS_PROVIDER"]).present?
|
86
|
+
vars << ptr # Intentionally not a return
|
87
|
+
else
|
88
|
+
vars.push(*KNOWN_REDIS_URL_ENVS)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
vars.select!(&:present?)
|
93
|
+
|
94
|
+
vars.each do |e|
|
95
|
+
return ENV[e] if ENV[e].present?
|
96
|
+
end
|
97
|
+
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize_pool(options)
|
102
|
+
new(timeout: options[:timeout], size: options[:size]) do
|
103
|
+
namespace = options[:namespace]
|
104
|
+
client = Redis.new client_opts(options)
|
105
|
+
|
106
|
+
if namespace
|
107
|
+
begin
|
108
|
+
require "redis/namespace"
|
109
|
+
Redis::Namespace.new(namespace, redis: client)
|
110
|
+
rescue LoadError
|
111
|
+
Rails.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
|
112
|
+
"Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
|
113
|
+
exit(-127)
|
114
|
+
end
|
115
|
+
else
|
116
|
+
client
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Mainly intended for console use
|
123
|
+
def lazy
|
124
|
+
RedisProxy.new do |&blk|
|
125
|
+
with(&blk)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def lazy_with(&blk)
|
130
|
+
return lazy unless blk
|
131
|
+
with(&blk)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Very naughty, but looking at the source, should be safe
|
135
|
+
def resize!(new_size)
|
136
|
+
return unless new_size > @size
|
137
|
+
@size = new_size
|
138
|
+
@available.instance_variable_set(:@max, new_size)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RediConn
|
2
|
+
module RedisModel
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def redis_attr(key, type = :string, read_only: true)
|
7
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
8
|
+
def #{key}=(value)
|
9
|
+
raise "#{key} is read-only once the batch has been started" if #{read_only.to_s} && (@initialized || @existing)
|
10
|
+
@#{key} = value
|
11
|
+
if :#{type} == :json
|
12
|
+
value = JSON.unparse(value)
|
13
|
+
end
|
14
|
+
persist_bid_attr('#{key}', value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def #{key}
|
18
|
+
return @#{key} if defined?(@#{key})
|
19
|
+
if (@initialized || @existing)
|
20
|
+
value = read_bid_attr('#{key}')
|
21
|
+
if :#{type} == :bool
|
22
|
+
value = value == 'true'
|
23
|
+
elsif :#{type} == :int
|
24
|
+
value = value.to_i
|
25
|
+
elsif :#{type} == :float
|
26
|
+
value = value.to_f
|
27
|
+
elsif :#{type} == :json
|
28
|
+
value = JSON.parse(value)
|
29
|
+
elsif :#{type} == :symbol
|
30
|
+
value = value&.to_sym
|
31
|
+
end
|
32
|
+
@#{key} = value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def persist_bid_attr(attribute, value)
|
40
|
+
if @initialized || @existing
|
41
|
+
redis do |r|
|
42
|
+
r.multi do |r|
|
43
|
+
r.hset(redis_key, attribute, value.to_s)
|
44
|
+
r.expire(redis_key, Batch::BID_EXPIRE_TTL)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
@pending_attrs ||= {}
|
49
|
+
@pending_attrs[attribute] = value.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_bid_attr(attribute)
|
54
|
+
redis do |r|
|
55
|
+
r.hget(redis_key, attribute)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def flush_pending_attrs
|
60
|
+
redis do |r|
|
61
|
+
r.mapped_hmset(redis_key, @pending_attrs)
|
62
|
+
end
|
63
|
+
@initialized = true
|
64
|
+
@pending_attrs = {}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module RediConn
|
2
|
+
# Mainly intended for console use
|
3
|
+
class RedisProxy
|
4
|
+
def initialize(&factory)
|
5
|
+
@factory = factory
|
6
|
+
end
|
7
|
+
|
8
|
+
def multi(*args, &block)
|
9
|
+
@factory.call do |r|
|
10
|
+
r.multi(*args) do |r|
|
11
|
+
block.call(r)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def pipelined(*args, &block)
|
17
|
+
@factory.call do |r|
|
18
|
+
r.pipelined(*args) do |r2|
|
19
|
+
block.call(r2 || r)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def uget(key)
|
25
|
+
@factory.call do |r|
|
26
|
+
case r.type(key)
|
27
|
+
when 'string'
|
28
|
+
r.get(key)
|
29
|
+
when 'list'
|
30
|
+
r.lrange(key, 0, -1)
|
31
|
+
when 'hash'
|
32
|
+
r.hgetall(key)
|
33
|
+
when 'set'
|
34
|
+
r.smembers(key)
|
35
|
+
when 'zset'
|
36
|
+
r.zrange(key, 0, -1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(method_name, *arguments, &block)
|
42
|
+
@factory.call do |r|
|
43
|
+
r.send(method_name, *arguments, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond_to_missing?(method_name, include_private = false)
|
48
|
+
super || Redis.method_defined?(method_name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
# Modified from https://github.com/Shopify/wolverine/blob/master/lib/wolverine/script.rb
|
6
|
+
|
7
|
+
module RediConn
|
8
|
+
# {RedisScript} represents a lua script in the filesystem. It loads the script
|
9
|
+
# from disk and handles talking to redis to execute it. Error handling
|
10
|
+
# is handled by {LuaError}.
|
11
|
+
class RedisScript
|
12
|
+
|
13
|
+
# Loads the script file from disk and calculates its +SHA1+ sum.
|
14
|
+
#
|
15
|
+
# @param file [Pathname] the full path to the indicated file
|
16
|
+
def initialize(file)
|
17
|
+
@file = Pathname.new(file)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Passes the script and supplied arguments to redis for evaulation.
|
21
|
+
# It first attempts to use a script redis has already cached by using
|
22
|
+
# the +EVALSHA+ command, but falls back to providing the full script
|
23
|
+
# text via +EVAL+ if redis has not seen this script before. Future
|
24
|
+
# invocations will then use +EVALSHA+ without erroring.
|
25
|
+
#
|
26
|
+
# @param redis [Redis] the redis connection to run against
|
27
|
+
# @param args [*Objects] the arguments to the script
|
28
|
+
# @return [Object] the value passed back by redis after script execution
|
29
|
+
# @raise [LuaError] if the script failed to compile of encountered a
|
30
|
+
# runtime error
|
31
|
+
def call(redis, *args)
|
32
|
+
t = Time.now
|
33
|
+
begin
|
34
|
+
redis.evalsha(digest, *args)
|
35
|
+
rescue => e
|
36
|
+
e.message =~ /NOSCRIPT/ ? redis.eval(content, *args) : raise
|
37
|
+
end
|
38
|
+
rescue => e
|
39
|
+
if LuaError.intercepts?(e)
|
40
|
+
raise LuaError.new(e, @file, content)
|
41
|
+
else
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def content
|
47
|
+
@content ||= load_lua(@file)
|
48
|
+
end
|
49
|
+
|
50
|
+
def digest
|
51
|
+
@digest ||= Digest::SHA1.hexdigest content
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def script_path
|
57
|
+
Rails.root + 'app/redis_lua'
|
58
|
+
end
|
59
|
+
|
60
|
+
def relative_path
|
61
|
+
@path ||= @file.relative_path_from(script_path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_lua(file)
|
65
|
+
TemplateContext.new(script_path).template(script_path + file)
|
66
|
+
end
|
67
|
+
|
68
|
+
class TemplateContext
|
69
|
+
def initialize(script_path)
|
70
|
+
@script_path = script_path
|
71
|
+
end
|
72
|
+
|
73
|
+
def template(pathname)
|
74
|
+
@partial_templates ||= {}
|
75
|
+
ERB.new(File.read(pathname)).result binding
|
76
|
+
end
|
77
|
+
|
78
|
+
# helper method to include a lua partial within another lua script
|
79
|
+
#
|
80
|
+
# @param relative_path [String] the relative path to the script from
|
81
|
+
# `script_path`
|
82
|
+
def include_partial(relative_path)
|
83
|
+
unless @partial_templates.has_key? relative_path
|
84
|
+
@partial_templates[relative_path] = nil
|
85
|
+
template( Pathname.new("#{@script_path}/#{relative_path}") )
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Reformats errors raised by redis representing failures while executing
|
91
|
+
# a lua script. The default errors have confusing messages and backtraces,
|
92
|
+
# and a type of +RuntimeError+. This class improves the message and
|
93
|
+
# modifies the backtrace to include the lua script itself in a reasonable
|
94
|
+
# way.
|
95
|
+
class LuaError < StandardError
|
96
|
+
PATTERN = /ERR Error (compiling|running) script \(.*?\): .*?:(\d+): (.*)/
|
97
|
+
WOLVERINE_LIB_PATH = File.expand_path('../../', __FILE__)
|
98
|
+
CONTEXT_LINE_NUMBER = 2
|
99
|
+
|
100
|
+
attr_reader :error, :file, :content
|
101
|
+
|
102
|
+
# Is this error one that should be reformatted?
|
103
|
+
#
|
104
|
+
# @param error [StandardError] the original error raised by redis
|
105
|
+
# @return [Boolean] is this an error that should be reformatted?
|
106
|
+
def self.intercepts? error
|
107
|
+
error.message =~ PATTERN
|
108
|
+
end
|
109
|
+
|
110
|
+
# Initialize a new {LuaError} from an existing redis error, adjusting
|
111
|
+
# the message and backtrace in the process.
|
112
|
+
#
|
113
|
+
# @param error [StandardError] the original error raised by redis
|
114
|
+
# @param file [Pathname] full path to the lua file the error ocurred in
|
115
|
+
# @param content [String] lua file content the error ocurred in
|
116
|
+
def initialize error, file, content
|
117
|
+
@error = error
|
118
|
+
@file = file
|
119
|
+
@content = content
|
120
|
+
|
121
|
+
@error.message =~ PATTERN
|
122
|
+
_stage, line_number, message = $1, $2, $3
|
123
|
+
error_context = generate_error_context(content, line_number.to_i)
|
124
|
+
|
125
|
+
super "#{message}\n\n#{error_context}\n\n"
|
126
|
+
set_backtrace generate_backtrace file, line_number
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def generate_error_context(content, line_number)
|
132
|
+
lines = content.lines.to_a
|
133
|
+
beginning_line_number = [1, line_number - CONTEXT_LINE_NUMBER].max
|
134
|
+
ending_line_number = [lines.count, line_number + CONTEXT_LINE_NUMBER].min
|
135
|
+
line_number_width = ending_line_number.to_s.length
|
136
|
+
|
137
|
+
(beginning_line_number..ending_line_number).map do |number|
|
138
|
+
indicator = number == line_number ? '=>' : ' '
|
139
|
+
formatted_number = "%#{line_number_width}d" % number
|
140
|
+
" #{indicator} #{formatted_number}: #{lines[number - 1]}"
|
141
|
+
end.join.chomp
|
142
|
+
end
|
143
|
+
|
144
|
+
def generate_backtrace(file, line_number)
|
145
|
+
pre_wolverine = backtrace_before_entering_wolverine(@error.backtrace)
|
146
|
+
index_of_first_wolverine_line = (@error.backtrace.size - pre_wolverine.size - 1)
|
147
|
+
pre_wolverine.unshift(@error.backtrace[index_of_first_wolverine_line])
|
148
|
+
pre_wolverine.unshift("#{file}:#{line_number}")
|
149
|
+
pre_wolverine
|
150
|
+
end
|
151
|
+
|
152
|
+
def backtrace_before_entering_wolverine(backtrace)
|
153
|
+
backtrace.reverse.take_while { |line| ! line_from_wolverine(line) }.reverse
|
154
|
+
end
|
155
|
+
|
156
|
+
def line_from_wolverine(line)
|
157
|
+
line.split(':').first.include?(WOLVERINE_LIB_PATH)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/rediconn.rb
ADDED
data/rediconn.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
begin
|
6
|
+
require "rediconn/version"
|
7
|
+
version = RediConn::VERSION
|
8
|
+
rescue LoadError
|
9
|
+
version = "0.0.0.docker"
|
10
|
+
end
|
11
|
+
|
12
|
+
Gem::Specification.new do |spec|
|
13
|
+
spec.name = "rediconn"
|
14
|
+
spec.version = version
|
15
|
+
spec.authors = ["Ethan Knapp"]
|
16
|
+
spec.email = ["eknapp@instructure.com"]
|
17
|
+
|
18
|
+
spec.summary = "Gem for consistent, de-duplicated Redis Connection Pooling"
|
19
|
+
spec.homepage = "https://instructure.com"
|
20
|
+
|
21
|
+
spec.files = Dir["{app,config,db,lib}/**/*", "README.md", "*.gemspec"]
|
22
|
+
spec.test_files = Dir["spec/**/*"]
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency "rails", ">= 5.2", "< 9.0"
|
26
|
+
spec.add_dependency "connection_pool", ">= 2.2.0", "< 3.0"
|
27
|
+
|
28
|
+
spec.add_development_dependency "redis"
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3'
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RediConn::RedisConnection do
|
4
|
+
let(:conn) { described_class.create(env_prefix: "REDICONN") }
|
5
|
+
|
6
|
+
describe '.redis' do
|
7
|
+
it 'returns the same connection if called when nested' do
|
8
|
+
conn.lazy_with do |r1|
|
9
|
+
conn.lazy_with do |r2|
|
10
|
+
expect(r1).to be r2
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns a RedisProxy object if no block is given' do
|
16
|
+
expect(conn.lazy).to be_a(RediConn::RedisProxy)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'RedisProxy' do
|
20
|
+
let!(:proxy) { conn.lazy }
|
21
|
+
|
22
|
+
it 'calls Batch.redis with a block' do
|
23
|
+
expect(conn).to receive(:with) do |&block|
|
24
|
+
expect(block).to be_a Proc
|
25
|
+
end
|
26
|
+
proxy.sadd('KEY', 5)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#multi' do
|
30
|
+
it 'works with 1 arity' do
|
31
|
+
proxy.multi do |r|
|
32
|
+
r.sadd('KEY', 5)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rediconn
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ethan Knapp
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rails
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '5.2'
|
19
|
+
- - "<"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '9.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '5.2'
|
29
|
+
- - "<"
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '9.0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: connection_pool
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 2.2.0
|
39
|
+
- - "<"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '3.0'
|
42
|
+
type: :runtime
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 2.2.0
|
49
|
+
- - "<"
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '3.0'
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: redis
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
type: :development
|
60
|
+
prerelease: false
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: rspec
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - "~>"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '3'
|
73
|
+
type: :development
|
74
|
+
prerelease: false
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '3'
|
80
|
+
email:
|
81
|
+
- eknapp@instructure.com
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- README.md
|
87
|
+
- lib/rediconn.rb
|
88
|
+
- lib/rediconn/redis_connection.rb
|
89
|
+
- lib/rediconn/redis_model.rb
|
90
|
+
- lib/rediconn/redis_proxy.rb
|
91
|
+
- lib/rediconn/redis_script.rb
|
92
|
+
- lib/rediconn/version.rb
|
93
|
+
- rediconn.gemspec
|
94
|
+
- spec/rediconn/redis_connection_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
homepage: https://instructure.com
|
97
|
+
licenses: []
|
98
|
+
metadata: {}
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubygems_version: 3.6.9
|
114
|
+
specification_version: 4
|
115
|
+
summary: Gem for consistent, de-duplicated Redis Connection Pooling
|
116
|
+
test_files:
|
117
|
+
- spec/rediconn/redis_connection_spec.rb
|
118
|
+
- spec/spec_helper.rb
|