redcord 0.0.1.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/redcord.rb +11 -0
- data/lib/redcord.rbi +94 -0
- data/lib/redcord/actions.rb +135 -0
- data/lib/redcord/attribute.rb +75 -0
- data/lib/redcord/base.rb +59 -0
- data/lib/redcord/configurations.rb +72 -0
- data/lib/redcord/logger.rb +69 -0
- data/lib/redcord/lua_script_reader.rb +16 -0
- data/lib/redcord/migration.rb +27 -0
- data/lib/redcord/migration/migrator.rb +74 -0
- data/lib/redcord/migration/ttl.rb +11 -0
- data/lib/redcord/migration/version.rb +40 -0
- data/lib/redcord/prepared_redis.rb +18 -0
- data/lib/redcord/railtie.rb +16 -0
- data/lib/redcord/range_interval.rb +9 -0
- data/lib/redcord/redis_connection.rb +103 -0
- data/lib/redcord/relation.rb +95 -0
- data/lib/redcord/serializer.rb +129 -0
- data/lib/redcord/server_scripts.rb +78 -0
- data/lib/redcord/server_scripts/create_hash_returning_id.erb.lua +68 -0
- data/lib/redcord/server_scripts/delete_hash.erb.lua +48 -0
- data/lib/redcord/server_scripts/find_by_attr.erb.lua +67 -0
- data/lib/redcord/server_scripts/find_by_attr_count.erb.lua +52 -0
- data/lib/redcord/server_scripts/shared/index_helper_methods.erb.lua +61 -0
- data/lib/redcord/server_scripts/shared/lua_helper_methods.erb.lua +33 -0
- data/lib/redcord/server_scripts/shared/query_helper_methods.erb.lua +105 -0
- data/lib/redcord/server_scripts/update_hash.erb.lua +91 -0
- data/lib/redcord/tasks/redis.rake +34 -0
- metadata +210 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
-- Add an id to the id set of the index attribute
|
2
|
+
local function add_id_to_index_attr(model, attr_key, attr_val, id)
|
3
|
+
if attr_val then
|
4
|
+
-- Call the Redis command: SADD "#{Model.name}:#{attr_name}:#{attr_val}" member ..
|
5
|
+
redis.call('sadd', model .. ':' .. attr_key .. ':' .. attr_val, id)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
-- Remove an id from the id set of the index attribute
|
10
|
+
local function delete_id_from_index_attr(model, attr_key, attr_val, id)
|
11
|
+
if attr_val then
|
12
|
+
-- Call the Redis command: SREM "#{Model.name}:#{attr_name}:#{attr_val}" member ..
|
13
|
+
redis.call('srem', model .. ':' .. attr_key .. ':' .. attr_val, id)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
-- Move an id from one id set to another for the index attribute
|
18
|
+
local function replace_id_in_index_attr(model, attr_key, prev_attr_val,curr_attr_val, id)
|
19
|
+
-- If previous and new value differs, then modify the id sets accordingly
|
20
|
+
if prev_attr_val ~= curr_attr_val then
|
21
|
+
delete_id_from_index_attr(model, attr_key, prev_attr_val, id)
|
22
|
+
add_id_to_index_attr(model, attr_key, curr_attr_val, id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
-- Add an id to the sorted id set of the range index attribute
|
27
|
+
local function add_id_to_range_index_attr(model, attr_key, attr_val, id)
|
28
|
+
if attr_val then
|
29
|
+
-- Nil values of range indices are sent to Redis as an empty string. They are stored
|
30
|
+
-- as a regular set at key "#{Model.name}:#{attr_name}:"
|
31
|
+
if attr_val == "" then
|
32
|
+
redis.call('sadd', model .. ':' .. attr_key .. ':' .. attr_val, id)
|
33
|
+
else
|
34
|
+
-- Call the Redis command: ZADD "#{Model.name}:#{attr_name}" #{attr_val} member ..,
|
35
|
+
-- where attr_val is the score of the sorted set
|
36
|
+
redis.call('zadd', model .. ':' .. attr_key, attr_val, id)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
-- Remove an id from the sorted id set of the range index attribute
|
42
|
+
local function delete_id_from_range_index_attr(model, attr_key, attr_val, id)
|
43
|
+
if attr_val then
|
44
|
+
-- Nil values of range indices are sent to Redis as an empty string. They are stored
|
45
|
+
-- as a regular set at key "#{Model.name}:#{attr_name}:"
|
46
|
+
if attr_val == "" then
|
47
|
+
redis.call('srem', model .. ':' .. attr_key .. ':' .. attr_val, id)
|
48
|
+
else
|
49
|
+
-- Call the Redis command: ZREM "#{Model.name}:#{attr_name}:#{attr_val}" member ..
|
50
|
+
redis.call('zrem', model .. ':' .. attr_key, id)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
-- Move an id from one sorted id set to another for the range index attribute
|
56
|
+
local function replace_id_in_range_index_attr(model, attr_key, prev_attr_val, curr_attr_val, id)
|
57
|
+
if prev_attr_val ~= curr_attr_val then
|
58
|
+
delete_id_from_range_index_attr(model, attr_key, prev_attr_val, id)
|
59
|
+
add_id_to_range_index_attr(model, attr_key, curr_attr_val, id)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
-- Helper function to convert argument array to hash set
|
2
|
+
local function to_hash(list)
|
3
|
+
local hash = {}
|
4
|
+
if not list then return hash end
|
5
|
+
for i=1, #list, 2 do
|
6
|
+
hash[list[i]] = list[i+1]
|
7
|
+
end
|
8
|
+
return hash
|
9
|
+
end
|
10
|
+
|
11
|
+
-- Helper function to convert list to set
|
12
|
+
local function to_set(list)
|
13
|
+
local set = {}
|
14
|
+
if not list then return set end
|
15
|
+
for _, item in ipairs(list) do
|
16
|
+
set[item] = true
|
17
|
+
end
|
18
|
+
return set
|
19
|
+
end
|
20
|
+
|
21
|
+
-- Helper function to compute the intersection of the given set and list.
|
22
|
+
local function set_list_intersect(set, list)
|
23
|
+
-- A nil set means that no items have been added to the set yet. If so,
|
24
|
+
-- we can just return the given list as a set
|
25
|
+
if not set then return to_set(list) end
|
26
|
+
local set_intersect = {}
|
27
|
+
for _, item in ipairs(list) do
|
28
|
+
if set[item] then
|
29
|
+
set_intersect[item] = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
return set_intersect
|
33
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
-- Calls the Redis command: ZRANGEBYSCORE key min max
|
2
|
+
-- for each {key, min, max} given in the input arguments. Returns
|
3
|
+
-- the set intersection of the results
|
4
|
+
local function intersect_range_index_sets(set, tuples)
|
5
|
+
for _, redis_key in ipairs(tuples) do
|
6
|
+
local key, min, max = unpack(redis_key)
|
7
|
+
local ids = redis.call('zrangebyscore', key, min, max)
|
8
|
+
set = set_list_intersect(set, ids)
|
9
|
+
end
|
10
|
+
return set
|
11
|
+
end
|
12
|
+
|
13
|
+
-- Gets the hash of all the ids given. Returns the results in a
|
14
|
+
-- table, as well as any ids not found in Redis as a separate table
|
15
|
+
local function batch_hget(model, ids_set, fields)
|
16
|
+
local res, stale_ids = {}, {}
|
17
|
+
for id, _ in pairs(ids_set) do
|
18
|
+
local instance = nil
|
19
|
+
if fields and #fields > 0 then
|
20
|
+
local values = redis.call('hmget', model .. ':id:' .. id, unpack(fields))
|
21
|
+
-- HMGET returns the value in the order of the fields given. Map back to
|
22
|
+
-- field value [field value ..]
|
23
|
+
instance = {}
|
24
|
+
for i, field in ipairs(fields) do
|
25
|
+
if not values[i] then
|
26
|
+
instance = nil
|
27
|
+
break
|
28
|
+
end
|
29
|
+
table.insert(instance, field)
|
30
|
+
table.insert(instance, values[i])
|
31
|
+
end
|
32
|
+
else
|
33
|
+
-- HGETALL returns the value as field value [field value ..]
|
34
|
+
instance = redis.call('hgetall', model .. ':id:' .. id)
|
35
|
+
end
|
36
|
+
-- Only add to result if entry is not stale (if query to hgetall is not empty)
|
37
|
+
if instance and #instance > 0 then
|
38
|
+
-- We cannot return a Lua table to Redis as a hash. Return result as a flattened
|
39
|
+
-- array instead
|
40
|
+
table.insert(res, id)
|
41
|
+
table.insert(res, instance)
|
42
|
+
else
|
43
|
+
table.insert(stale_ids, id)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return {res, stale_ids}
|
47
|
+
end
|
48
|
+
|
49
|
+
-- Returns the number of ids which exist in the given ids_set
|
50
|
+
local function batch_exists(model, ids_set)
|
51
|
+
local id_keys = {}
|
52
|
+
for id, _ in pairs(ids_set) do
|
53
|
+
table.insert(id_keys, model .. ':id:' .. id)
|
54
|
+
end
|
55
|
+
return redis.call('exists', unpack(id_keys))
|
56
|
+
end
|
57
|
+
|
58
|
+
-- Validate that each item in the attr_vals table is not nil
|
59
|
+
local function validate_attr_vals(attr_key, attr_vals)
|
60
|
+
if not attr_vals or #attr_vals == 0 then
|
61
|
+
error('Invalid value given for attribute : ' .. attr_key)
|
62
|
+
end
|
63
|
+
for _, val in ipairs(attr_vals) do
|
64
|
+
if not val then
|
65
|
+
error('Invalid value given for attribute : ' .. attr_key)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
-- Validate the query conditions by checking if the attributes queried for are indexed
|
71
|
+
-- attributes. Parse query conditions into two separate tables:
|
72
|
+
-- 1. index_sets formatted as the id set keys in Redis '#{Model.name}:#{attr_key}:#{attr_val}'
|
73
|
+
-- 2. range_index_sets formatted as a tuple {id set key, min, max} => { '#{Model.name}:#{attr_key}' min max }
|
74
|
+
local function validate_and_parse_query_conditions(model, args)
|
75
|
+
local index_attrs = to_set(redis.call('smembers', model .. ':index_attrs'))
|
76
|
+
local range_index_attrs = to_set(redis.call('smembers', model .. ':range_index_attrs'))
|
77
|
+
-- Iterate through the arguments of the script to form the redis keys at which the
|
78
|
+
-- indexed id sets are stored.
|
79
|
+
local index_sets, range_index_sets = {}, {}
|
80
|
+
local i = 2
|
81
|
+
while i <= #args do
|
82
|
+
local attr_key, attr_val = args[i], args[i+1]
|
83
|
+
if index_attrs[attr_key] then
|
84
|
+
validate_attr_vals(attr_key, {attr_val})
|
85
|
+
-- For normal index attributes, keys are stored at "#{Model.name}:#{attr_key}:#{attr_val}"
|
86
|
+
table.insert(index_sets, model .. ':' .. attr_key .. ':' .. attr_val)
|
87
|
+
i = i + 2
|
88
|
+
elseif range_index_attrs[attr_key] then
|
89
|
+
-- For range attributes, nil values are stored as normal sets
|
90
|
+
if attr_val == "" then
|
91
|
+
table.insert(index_sets, model .. ':' .. attr_key .. ':' .. attr_val)
|
92
|
+
i = i + 2
|
93
|
+
else
|
94
|
+
local min, max = args[i+1], args[i+2]
|
95
|
+
validate_attr_vals(attr_key, {min, max})
|
96
|
+
-- For range index attributes, they are stored at "#{Model.name}:#{attr_key}"
|
97
|
+
table.insert(range_index_sets, {model .. ':' .. attr_key, min, max})
|
98
|
+
i = i + 3
|
99
|
+
end
|
100
|
+
else
|
101
|
+
error(attr_key .. ' is not an indexed attribute')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
return {index_sets, range_index_sets}
|
105
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
--[[
|
2
|
+
EVALSHA SHA1(__FILE__) model id [field value ...]
|
3
|
+
> Time complexity: O(N) where N is the number of fields being set.
|
4
|
+
|
5
|
+
Update a hash with the specified fields to their respective values stored at
|
6
|
+
"model":id:"id", and modify the indexed attribute id sets accordingly. Refresh
|
7
|
+
the ttl if a model's ttl exists and is set to a value other than -1
|
8
|
+
|
9
|
+
# Return value
|
10
|
+
nil
|
11
|
+
--]]
|
12
|
+
|
13
|
+
-- The arguments can be accessed by Lua using the KEYS global variable in the
|
14
|
+
-- form of a one-based array (so KEYS[1], KEYS[2], ...).
|
15
|
+
-- All the additional arguments should not represent key names and can be
|
16
|
+
-- accessed by Lua using the ARGV global variable, very similarly to what
|
17
|
+
-- happens with keys (so ARGV[1], ARGV[2], ...).
|
18
|
+
--
|
19
|
+
-- KEYS[1] = redcord_instance.class.name
|
20
|
+
-- KEYS[2] = redcord_instance.id
|
21
|
+
-- ARGV[1...2N] = attr_key attr_val [attr_key attr_val ..]
|
22
|
+
<%= include_lua 'shared/lua_helper_methods' %>
|
23
|
+
<%= include_lua 'shared/index_helper_methods' %>
|
24
|
+
|
25
|
+
if #KEYS ~= 2 then
|
26
|
+
error('Expected keys of be of size 2')
|
27
|
+
end
|
28
|
+
if #ARGV % 2 ~= 0 then
|
29
|
+
error('Expected an even number of arguments')
|
30
|
+
end
|
31
|
+
|
32
|
+
local model = KEYS[1]
|
33
|
+
local id = KEYS[2]
|
34
|
+
|
35
|
+
-- key = "#{model}:id:{id}"
|
36
|
+
local key = model .. ':id:' .. id
|
37
|
+
|
38
|
+
-- If there a delete operation (including expiring due to TTL) happened before
|
39
|
+
-- the update in another thread, the client might still send an update command
|
40
|
+
-- to the server. To avoid saving partial data, we reject this update call with
|
41
|
+
-- an error.
|
42
|
+
if redis.call('exists', key) == 0 then
|
43
|
+
error(key .. ' has been deleted')
|
44
|
+
end
|
45
|
+
|
46
|
+
-- Modify the id sets for any indexed attributes
|
47
|
+
local attrs_hash = to_hash(ARGV)
|
48
|
+
local indexed_attr_keys = redis.call('smembers', model .. ':index_attrs')
|
49
|
+
if #indexed_attr_keys > 0 then
|
50
|
+
-- Get the previous and new values for indexed attributes
|
51
|
+
local prev_attrs = redis.call('hmget', key, unpack(indexed_attr_keys))
|
52
|
+
for i, attr_key in ipairs(indexed_attr_keys) do
|
53
|
+
local prev_attr_val, curr_attr_val = prev_attrs[i], attrs_hash[attr_key]
|
54
|
+
-- Skip attr values not present in the argument hash
|
55
|
+
if curr_attr_val then
|
56
|
+
replace_id_in_index_attr(model, attr_key, prev_attr_val, curr_attr_val, id)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
local range_index_attr_keys = redis.call('smembers', model .. ':range_index_attrs')
|
61
|
+
if #range_index_attr_keys > 0 then
|
62
|
+
-- Get the previous and new values for indexed attributes
|
63
|
+
local prev_attrs = redis.call('hmget', key, unpack(range_index_attr_keys))
|
64
|
+
for i, attr_key in ipairs(range_index_attr_keys) do
|
65
|
+
local prev_attr_val, curr_attr_val = prev_attrs[i], attrs_hash[attr_key]
|
66
|
+
-- Skip attr values not present in the argument hash
|
67
|
+
if curr_attr_val then
|
68
|
+
replace_id_in_range_index_attr(model, attr_key, prev_attr_val, curr_attr_val, id)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
-- Forward the script arguments to the Redis command HSET and update the args.
|
74
|
+
-- Call the Redis command: HSET key [field value ...]
|
75
|
+
redis.call('hset', key, unpack(ARGV))
|
76
|
+
|
77
|
+
-- Call the Redis command: GET "#{Model.name}:ttl"
|
78
|
+
local ttl = redis.call('get', model .. ':ttl')
|
79
|
+
|
80
|
+
if ttl then
|
81
|
+
if ttl == '-1' then
|
82
|
+
-- Persist the object if the ttl is set to -1
|
83
|
+
redis.call('persist', key)
|
84
|
+
else
|
85
|
+
-- Reset the TTL for this object. We do this manually becaues altering the
|
86
|
+
-- field value of a hash with HSET, etc. will leave the TTL
|
87
|
+
-- untouched: https://redis.io/commands/expire
|
88
|
+
redis.call('expire', key, ttl)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
return nil
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require 'redcord/migration/version'
|
3
|
+
require 'redcord/migration/migrator'
|
4
|
+
|
5
|
+
db_namespace = namespace :redis do
|
6
|
+
task migrate: :environment do
|
7
|
+
$stdout.sync = true
|
8
|
+
migration_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
9
|
+
|
10
|
+
puts [
|
11
|
+
'redis',
|
12
|
+
'direction',
|
13
|
+
'version',
|
14
|
+
'migration',
|
15
|
+
'duration',
|
16
|
+
].map { |str| str.ljust(30) }.join("\t")
|
17
|
+
|
18
|
+
local_versions = Redcord::Migration::Version.new.all
|
19
|
+
Redcord::Base.configurations[Rails.env].each do |model, config|
|
20
|
+
redis = Redis.new(**(config.symbolize_keys))
|
21
|
+
remote_versions = Redcord::Migration::Version.new(redis: redis).all
|
22
|
+
(local_versions - remote_versions).sort.each do |version|
|
23
|
+
Redcord::Migration::Migrator.migrate(
|
24
|
+
redis: redis,
|
25
|
+
version: version,
|
26
|
+
direction: :up,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
migration_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
32
|
+
puts "\nFinished in #{(migration_end - migration_start).round(3)} seconds"
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redcord
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.alpha
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chan Zuckerberg Initiative
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-06-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: railties
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redis
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sorbet
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.4.4704
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.4.4704
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sorbet-coerce
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.2.7
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.2.7
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sorbet-runtime
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.4.4704
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.4.4704
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sorbet-static
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.4.4704
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.4.4704
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: codecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.2'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.2'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description:
|
154
|
+
email: opensource@chanzuckerberg.com
|
155
|
+
executables: []
|
156
|
+
extensions: []
|
157
|
+
extra_rdoc_files: []
|
158
|
+
files:
|
159
|
+
- lib/redcord.rb
|
160
|
+
- lib/redcord.rbi
|
161
|
+
- lib/redcord/actions.rb
|
162
|
+
- lib/redcord/attribute.rb
|
163
|
+
- lib/redcord/base.rb
|
164
|
+
- lib/redcord/configurations.rb
|
165
|
+
- lib/redcord/logger.rb
|
166
|
+
- lib/redcord/lua_script_reader.rb
|
167
|
+
- lib/redcord/migration.rb
|
168
|
+
- lib/redcord/migration/migrator.rb
|
169
|
+
- lib/redcord/migration/ttl.rb
|
170
|
+
- lib/redcord/migration/version.rb
|
171
|
+
- lib/redcord/prepared_redis.rb
|
172
|
+
- lib/redcord/railtie.rb
|
173
|
+
- lib/redcord/range_interval.rb
|
174
|
+
- lib/redcord/redis_connection.rb
|
175
|
+
- lib/redcord/relation.rb
|
176
|
+
- lib/redcord/serializer.rb
|
177
|
+
- lib/redcord/server_scripts.rb
|
178
|
+
- lib/redcord/server_scripts/create_hash_returning_id.erb.lua
|
179
|
+
- lib/redcord/server_scripts/delete_hash.erb.lua
|
180
|
+
- lib/redcord/server_scripts/find_by_attr.erb.lua
|
181
|
+
- lib/redcord/server_scripts/find_by_attr_count.erb.lua
|
182
|
+
- lib/redcord/server_scripts/shared/index_helper_methods.erb.lua
|
183
|
+
- lib/redcord/server_scripts/shared/lua_helper_methods.erb.lua
|
184
|
+
- lib/redcord/server_scripts/shared/query_helper_methods.erb.lua
|
185
|
+
- lib/redcord/server_scripts/update_hash.erb.lua
|
186
|
+
- lib/redcord/tasks/redis.rake
|
187
|
+
homepage: https://github.com/chanzuckerberg/redis-record
|
188
|
+
licenses:
|
189
|
+
- MIT
|
190
|
+
metadata: {}
|
191
|
+
post_install_message:
|
192
|
+
rdoc_options: []
|
193
|
+
require_paths:
|
194
|
+
- lib
|
195
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: 2.5.0
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - ">"
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: 1.3.1
|
205
|
+
requirements: []
|
206
|
+
rubygems_version: 3.1.3
|
207
|
+
signing_key:
|
208
|
+
specification_version: 4
|
209
|
+
summary: A Ruby ORM like Active Record, but for Redis
|
210
|
+
test_files: []
|