redcord 0.0.1.alpha → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/redcord.rb +30 -2
- data/lib/redcord.rbi +0 -16
- data/lib/redcord/actions.rb +171 -40
- data/lib/redcord/attribute.rb +126 -21
- data/lib/redcord/base.rb +15 -0
- data/lib/redcord/configurations.rb +4 -0
- data/lib/redcord/logger.rb +1 -1
- data/lib/redcord/lua_script_reader.rb +16 -5
- data/lib/redcord/migration.rb +2 -0
- data/lib/redcord/migration/index.rb +57 -0
- data/lib/redcord/migration/migrator.rb +1 -1
- data/lib/redcord/migration/ttl.rb +9 -4
- data/lib/redcord/migration/version.rb +3 -0
- data/lib/redcord/railtie.rb +18 -0
- data/lib/redcord/redis.rb +200 -0
- data/lib/redcord/redis_connection.rb +38 -29
- data/lib/redcord/relation.rb +214 -38
- data/lib/redcord/serializer.rb +147 -49
- data/lib/redcord/server_scripts/create_hash.erb.lua +81 -0
- data/lib/redcord/server_scripts/delete_hash.erb.lua +17 -8
- data/lib/redcord/server_scripts/find_by_attr.erb.lua +50 -16
- data/lib/redcord/server_scripts/find_by_attr_count.erb.lua +45 -14
- data/lib/redcord/server_scripts/shared/index_helper_methods.erb.lua +45 -16
- data/lib/redcord/server_scripts/shared/lua_helper_methods.erb.lua +20 -4
- data/lib/redcord/server_scripts/shared/query_helper_methods.erb.lua +86 -14
- data/lib/redcord/server_scripts/update_hash.erb.lua +40 -26
- data/lib/redcord/tasks/redis.rake +15 -0
- data/lib/redcord/tracer.rb +48 -0
- data/lib/redcord/vacuum_helper.rb +90 -0
- metadata +13 -11
- data/lib/redcord/prepared_redis.rb +0 -18
- data/lib/redcord/server_scripts.rb +0 -78
- data/lib/redcord/server_scripts/create_hash_returning_id.erb.lua +0 -68
data/lib/redcord/relation.rb
CHANGED
@@ -1,95 +1,271 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# typed: strict
|
4
|
+
|
5
|
+
require 'active_support/core_ext/array'
|
2
6
|
require 'active_support/core_ext/module'
|
3
7
|
|
8
|
+
module Redcord
|
9
|
+
class InvalidQuery < StandardError; end
|
10
|
+
end
|
11
|
+
|
4
12
|
class Redcord::Relation
|
5
13
|
extend T::Sig
|
6
14
|
|
7
15
|
sig { returns(T.class_of(Redcord::Base)) }
|
8
16
|
attr_reader :model
|
9
|
-
|
10
|
-
sig { returns(T::Hash[Symbol, T.untyped]) }
|
11
|
-
attr_reader :query_conditions
|
12
17
|
|
13
18
|
sig { returns(T::Set[Symbol]) }
|
14
19
|
attr_reader :select_attrs
|
15
20
|
|
16
|
-
|
17
|
-
|
21
|
+
sig { returns(T.nilable(Symbol)) }
|
22
|
+
attr_reader :custom_index_name
|
18
23
|
|
19
|
-
sig
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def map(&blk)
|
25
|
-
to_a.map(&blk)
|
26
|
-
end
|
24
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
25
|
+
attr_reader :regular_index_query_conditions
|
26
|
+
|
27
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
28
|
+
attr_reader :custom_index_query_conditions
|
27
29
|
|
28
30
|
sig do
|
29
31
|
params(
|
30
32
|
model: T.class_of(Redcord::Base),
|
31
|
-
|
32
|
-
|
33
|
+
regular_index_query_conditions: T::Hash[Symbol, T.untyped],
|
34
|
+
custom_index_query_conditions: T::Hash[Symbol, T.untyped],
|
35
|
+
select_attrs: T::Set[Symbol],
|
36
|
+
custom_index_name: T.nilable(Symbol)
|
33
37
|
).void
|
34
38
|
end
|
35
|
-
def initialize(
|
39
|
+
def initialize(
|
40
|
+
model,
|
41
|
+
regular_index_query_conditions = {},
|
42
|
+
custom_index_query_conditions = {},
|
43
|
+
select_attrs = Set.new,
|
44
|
+
custom_index_name: nil
|
45
|
+
)
|
36
46
|
@model = model
|
37
|
-
@
|
47
|
+
@regular_index_query_conditions = regular_index_query_conditions
|
48
|
+
@custom_index_query_conditions = custom_index_query_conditions
|
38
49
|
@select_attrs = select_attrs
|
50
|
+
@custom_index_name = custom_index_name
|
39
51
|
end
|
40
52
|
|
41
53
|
sig { params(args: T::Hash[Symbol, T.untyped]).returns(Redcord::Relation) }
|
42
54
|
def where(args)
|
43
55
|
encoded_args = args.map do |attr_key, attr_val|
|
44
|
-
encoded_val = model.
|
56
|
+
encoded_val = model.validate_types_and_encode_query(attr_key, attr_val)
|
45
57
|
[attr_key, encoded_val]
|
46
58
|
end
|
47
|
-
|
59
|
+
|
60
|
+
regular_index_query_conditions.merge!(encoded_args.to_h)
|
61
|
+
if custom_index_name
|
62
|
+
with_index(custom_index_name)
|
63
|
+
end
|
48
64
|
self
|
49
65
|
end
|
50
66
|
|
51
67
|
sig do
|
52
68
|
params(
|
53
|
-
args:
|
69
|
+
args: T.untyped,
|
54
70
|
blk: T.nilable(T.proc.params(arg0: T.untyped).void),
|
55
71
|
).returns(T.any(Redcord::Relation, T::Array[T.untyped]))
|
56
72
|
end
|
57
73
|
def select(*args, &blk)
|
58
|
-
|
59
|
-
|
74
|
+
Redcord::Base.trace(
|
75
|
+
'redcord_relation_select',
|
76
|
+
model_name: model.name,
|
77
|
+
) do
|
78
|
+
if block_given?
|
79
|
+
return execute_query.select do |*item|
|
80
|
+
blk.call(*item)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
select_attrs.merge(args)
|
85
|
+
self
|
60
86
|
end
|
61
|
-
select_attrs.merge(args)
|
62
|
-
self
|
63
87
|
end
|
64
88
|
|
65
89
|
sig { returns(Integer) }
|
66
90
|
def count
|
67
|
-
|
91
|
+
Redcord::Base.trace(
|
92
|
+
'redcord_relation_count',
|
93
|
+
model_name: model.name,
|
94
|
+
) do
|
95
|
+
model.validate_index_attributes(query_conditions.keys, custom_index_name: custom_index_name)
|
96
|
+
redis.find_by_attr_count(
|
97
|
+
model.model_key,
|
98
|
+
extract_query_conditions!,
|
99
|
+
index_attrs: model._script_arg_index_attrs,
|
100
|
+
range_index_attrs: model._script_arg_range_index_attrs,
|
101
|
+
custom_index_attrs: model._script_arg_custom_index_attrs[custom_index_name],
|
102
|
+
hash_tag: extract_hash_tag!,
|
103
|
+
custom_index_name: custom_index_name
|
104
|
+
)
|
105
|
+
end
|
68
106
|
end
|
69
107
|
|
70
|
-
sig {
|
71
|
-
def
|
72
|
-
|
108
|
+
sig { params(index_name: T.nilable(Symbol)).returns(Redcord::Relation) }
|
109
|
+
def with_index(index_name)
|
110
|
+
@custom_index_name = index_name
|
111
|
+
adjusted_query_conditions = model.validate_and_adjust_custom_index_query_conditions(regular_index_query_conditions)
|
112
|
+
custom_index_query_conditions.merge!(adjusted_query_conditions)
|
113
|
+
self
|
73
114
|
end
|
74
115
|
|
116
|
+
delegate(
|
117
|
+
:&,
|
118
|
+
:[],
|
119
|
+
:all?,
|
120
|
+
:any?,
|
121
|
+
:any?,
|
122
|
+
:at,
|
123
|
+
:collect!,
|
124
|
+
:collect,
|
125
|
+
:compact!,
|
126
|
+
:compact,
|
127
|
+
:each,
|
128
|
+
:each_index,
|
129
|
+
:empty?,
|
130
|
+
:eql?,
|
131
|
+
:exists?,
|
132
|
+
:fetch,
|
133
|
+
:fifth!,
|
134
|
+
:fifth,
|
135
|
+
:filter!,
|
136
|
+
:filter,
|
137
|
+
:first!,
|
138
|
+
:first,
|
139
|
+
:forty_two!,
|
140
|
+
:forty_two,
|
141
|
+
:fourth!,
|
142
|
+
:fourth,
|
143
|
+
:include?,
|
144
|
+
:inspect,
|
145
|
+
:last!,
|
146
|
+
:last,
|
147
|
+
:many?,
|
148
|
+
:map!,
|
149
|
+
:map,
|
150
|
+
:none?,
|
151
|
+
:one?,
|
152
|
+
:reject!,
|
153
|
+
:reject,
|
154
|
+
:reverse!,
|
155
|
+
:reverse,
|
156
|
+
:reverse_each,
|
157
|
+
:second!,
|
158
|
+
:second,
|
159
|
+
:second_to_last!,
|
160
|
+
:second_to_last,
|
161
|
+
:size,
|
162
|
+
:sort!,
|
163
|
+
:sort,
|
164
|
+
:sort_by!,
|
165
|
+
:take!,
|
166
|
+
:take,
|
167
|
+
:third!,
|
168
|
+
:third,
|
169
|
+
:third_to_last!,
|
170
|
+
:third_to_last,
|
171
|
+
:to_a,
|
172
|
+
:to_ary,
|
173
|
+
:to_h,
|
174
|
+
:to_s,
|
175
|
+
:zip,
|
176
|
+
:|,
|
177
|
+
to: :execute_query,
|
178
|
+
)
|
179
|
+
|
75
180
|
private
|
181
|
+
|
182
|
+
sig { returns(T.nilable(String)) }
|
183
|
+
def extract_hash_tag!
|
184
|
+
attr = model.shard_by_attribute
|
185
|
+
return nil if attr.nil?
|
186
|
+
|
187
|
+
if !query_conditions.keys.include?(attr)
|
188
|
+
raise(
|
189
|
+
Redcord::InvalidQuery,
|
190
|
+
"Queries must contain attribute '#{attr}' since model #{model.name} is sharded by this attribute"
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Query conditions on custom index are always in form of range, even when query is by value condition is [value_x, value_x]
|
195
|
+
# When in fact query is by value, range is trasformed to a single value to pass the validation.
|
196
|
+
condition = query_conditions[attr]
|
197
|
+
if custom_index_name and condition.first == condition.last
|
198
|
+
condition = condition.first
|
199
|
+
end
|
200
|
+
case condition
|
201
|
+
when Integer, String
|
202
|
+
"{#{condition}}"
|
203
|
+
else
|
204
|
+
raise(
|
205
|
+
Redcord::InvalidQuery,
|
206
|
+
"Does not support query condition #{condition} on a Redis Cluster",
|
207
|
+
)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
76
211
|
sig { returns(T::Array[T.untyped]) }
|
77
212
|
def execute_query
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
213
|
+
Redcord::Base.trace(
|
214
|
+
'redcord_relation_execute_query',
|
215
|
+
model_name: model.name,
|
216
|
+
) do
|
217
|
+
model.validate_index_attributes(query_conditions.keys, custom_index_name: custom_index_name)
|
218
|
+
if !select_attrs.empty?
|
219
|
+
res_hash = redis.find_by_attr(
|
220
|
+
model.model_key,
|
221
|
+
extract_query_conditions!,
|
222
|
+
select_attrs: select_attrs,
|
223
|
+
index_attrs: model._script_arg_index_attrs,
|
224
|
+
range_index_attrs: model._script_arg_range_index_attrs,
|
225
|
+
custom_index_attrs: model._script_arg_custom_index_attrs[custom_index_name],
|
226
|
+
hash_tag: extract_hash_tag!,
|
227
|
+
custom_index_name: custom_index_name
|
228
|
+
)
|
229
|
+
|
230
|
+
res_hash.map do |id, args|
|
231
|
+
model.from_redis_hash(args).map do |k, v|
|
232
|
+
[k.to_sym, TypeCoerce[model.get_attr_type(k.to_sym)].new.from(v)]
|
233
|
+
end.to_h.merge(id: id)
|
234
|
+
end
|
235
|
+
else
|
236
|
+
res_hash = redis.find_by_attr(
|
237
|
+
model.model_key,
|
238
|
+
extract_query_conditions!,
|
239
|
+
index_attrs: model._script_arg_index_attrs,
|
240
|
+
range_index_attrs: model._script_arg_range_index_attrs,
|
241
|
+
custom_index_attrs: model._script_arg_custom_index_attrs[custom_index_name],
|
242
|
+
hash_tag: extract_hash_tag!,
|
243
|
+
custom_index_name: custom_index_name
|
244
|
+
)
|
245
|
+
|
246
|
+
res_hash.map { |id, args| model.coerce_and_set_id(args, id) }
|
84
247
|
end
|
85
|
-
else
|
86
|
-
res_hash = redis.find_by_attr(model.model_key, query_conditions)
|
87
|
-
return res_hash.map { |id, args| model.coerce_and_set_id(args, id) }
|
88
248
|
end
|
89
249
|
end
|
90
250
|
|
91
|
-
sig { returns(Redcord::
|
251
|
+
sig { returns(Redcord::Redis) }
|
92
252
|
def redis
|
93
253
|
model.redis
|
94
254
|
end
|
255
|
+
|
256
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
257
|
+
def query_conditions
|
258
|
+
custom_index_name ? custom_index_query_conditions : regular_index_query_conditions
|
259
|
+
end
|
260
|
+
|
261
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
262
|
+
def extract_query_conditions!
|
263
|
+
attr = model.shard_by_attribute
|
264
|
+
return query_conditions if attr.nil?
|
265
|
+
|
266
|
+
cond = query_conditions.reject { |key| key == attr }
|
267
|
+
raise Redcord::InvalidQuery, "Cannot query only by shard_by_attribute: #{attr}" if cond.empty?
|
268
|
+
|
269
|
+
cond
|
270
|
+
end
|
95
271
|
end
|
data/lib/redcord/serializer.rb
CHANGED
@@ -1,14 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# typed: strict
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
require 'redcord/range_interval'
|
6
|
+
|
6
7
|
module Redcord
|
7
8
|
# Raised by Model.where
|
8
9
|
class AttributeNotIndexed < StandardError; end
|
9
10
|
class WrongAttributeType < TypeError; end
|
11
|
+
class CustomIndexInvalidQuery < StandardError; end
|
12
|
+
class CustomIndexInvalidDesign < StandardError; end
|
10
13
|
end
|
11
14
|
|
15
|
+
# This module defines various helper methods on Redcord for serialization
|
16
|
+
# between the Ruby client and Redis server.
|
12
17
|
module Redcord::Serializer
|
13
18
|
extend T::Sig
|
14
19
|
|
@@ -16,84 +21,160 @@ module Redcord::Serializer
|
|
16
21
|
def self.included(klass)
|
17
22
|
klass.extend(ClassMethods)
|
18
23
|
end
|
19
|
-
|
24
|
+
|
20
25
|
module ClassMethods
|
21
26
|
extend T::Sig
|
22
27
|
|
23
|
-
# Redis only allows range queries on floats. To allow range queries on the
|
24
|
-
# type, encode_attr_value and decode_attr_value will implicitly
|
25
|
-
# Time attributes to a float.
|
28
|
+
# Redis only allows range queries on floats. To allow range queries on the
|
29
|
+
# Ruby Time type, encode_attr_value and decode_attr_value will implicitly
|
30
|
+
# encode and decode Time attributes to a float.
|
26
31
|
TIME_TYPES = T.let(Set[Time, T.nilable(Time)], T::Set[T.untyped])
|
32
|
+
|
27
33
|
sig { params(attribute: Symbol, val: T.untyped).returns(T.untyped) }
|
28
34
|
def encode_attr_value(attribute, val)
|
29
|
-
if val && TIME_TYPES.include?(props[attribute][:type])
|
30
|
-
|
35
|
+
if !val.blank? && TIME_TYPES.include?(props[attribute][:type])
|
36
|
+
time_in_nano_sec = val.to_i * 1_000_000_000
|
37
|
+
time_in_nano_sec >= 0 ? time_in_nano_sec + val.nsec : time_in_nano_sec - val.nsec
|
38
|
+
elsif val.is_a?(Float)
|
39
|
+
# Encode as round-trippable float64
|
40
|
+
'%1.16e' % [val]
|
41
|
+
else
|
42
|
+
val
|
31
43
|
end
|
32
|
-
val
|
33
44
|
end
|
34
45
|
|
35
46
|
sig { params(attribute: Symbol, val: T.untyped).returns(T.untyped) }
|
36
47
|
def decode_attr_value(attribute, val)
|
37
|
-
if val && TIME_TYPES.include?(props[attribute][:type])
|
38
|
-
val =
|
48
|
+
if !val.blank? && TIME_TYPES.include?(props[attribute][:type])
|
49
|
+
val = val.to_i
|
50
|
+
nsec = val >= 0 ? val % 1_000_000_000 : -val % 1_000_000_000
|
51
|
+
|
52
|
+
Time.zone.at(val / 1_000_000_000).change(nsec: nsec)
|
53
|
+
else
|
54
|
+
val
|
39
55
|
end
|
40
|
-
val
|
41
56
|
end
|
42
57
|
|
43
58
|
sig { params(attr_key: Symbol, attr_val: T.untyped).returns(T.untyped)}
|
44
|
-
def
|
45
|
-
# Validate
|
46
|
-
if !class_variable_get(:@@index_attributes).include?(attr_key) &&
|
47
|
-
!class_variable_get(:@@range_index_attributes).include?(attr_key)
|
48
|
-
raise Redcord::AttributeNotIndexed.new(
|
49
|
-
"#{attr_key} is not an indexed attribute."
|
50
|
-
)
|
51
|
-
end
|
52
|
-
# Validate attribute types for normal index attributes
|
59
|
+
def validate_types_and_encode_query(attr_key, attr_val)
|
60
|
+
# Validate attribute types for index attributes
|
53
61
|
attr_type = get_attr_type(attr_key)
|
54
|
-
if class_variable_get(:@@index_attributes).include?(attr_key)
|
62
|
+
if class_variable_get(:@@index_attributes).include?(attr_key) || attr_key == shard_by_attribute
|
55
63
|
validate_attr_type(attr_val, attr_type)
|
56
64
|
else
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
validate_attr_type(attr_val, attr_type)
|
63
|
-
end
|
64
|
-
# Range index attributes need to be further encoded into a format understood by the Lua script.
|
65
|
-
if attr_val != nil
|
65
|
+
validate_range_attr_types(attr_val, attr_type)
|
66
|
+
|
67
|
+
# Range index attributes need to be further encoded into a format
|
68
|
+
# understood by the Lua script.
|
69
|
+
unless attr_val.nil?
|
66
70
|
attr_val = encode_range_index_attr_val(attr_key, attr_val)
|
67
71
|
end
|
68
72
|
end
|
69
73
|
attr_val
|
70
74
|
end
|
71
75
|
|
72
|
-
|
76
|
+
# Validate that attributes queried for are index attributes
|
77
|
+
# For custom index: validate that attributes are present in specified index
|
78
|
+
sig { params(attr_keys: T::Array[Symbol], custom_index_name: T.nilable(Symbol)).void}
|
79
|
+
def validate_index_attributes(attr_keys, custom_index_name: nil)
|
80
|
+
custom_index_attributes = class_variable_get(:@@custom_index_attributes)[custom_index_name]
|
81
|
+
attr_keys.each do |attr_key|
|
82
|
+
next if attr_key == shard_by_attribute
|
83
|
+
|
84
|
+
if !custom_index_attributes.empty?
|
85
|
+
if !custom_index_attributes.include?(attr_key)
|
86
|
+
raise(
|
87
|
+
Redcord::AttributeNotIndexed,
|
88
|
+
"#{attr_key} is not a part of #{custom_index_name} index.",
|
89
|
+
)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
if !class_variable_get(:@@index_attributes).include?(attr_key) &&
|
93
|
+
!class_variable_get(:@@range_index_attributes).include?(attr_key)
|
94
|
+
raise(
|
95
|
+
Redcord::AttributeNotIndexed,
|
96
|
+
"#{attr_key} is not an indexed attribute.",
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Validate exclusive ranges not used; Change all query conditions to range form;
|
104
|
+
# The position of the attribute and type of query is validated on Lua side
|
105
|
+
sig { params(query_conditions: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped])}
|
106
|
+
def validate_and_adjust_custom_index_query_conditions(query_conditions)
|
107
|
+
adjusted_query_conditions = query_conditions.clone
|
108
|
+
query_conditions.each do |attr_key, condition|
|
109
|
+
if !condition.is_a?(Array)
|
110
|
+
adjusted_query_conditions[attr_key] = [condition, condition]
|
111
|
+
elsif condition[0].to_s[0] == '(' or condition[1].to_s[0] == '('
|
112
|
+
raise(Redcord::CustomIndexInvalidQuery, "Custom index doesn't support exclusive ranges")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
adjusted_query_conditions
|
116
|
+
end
|
117
|
+
|
118
|
+
sig {
|
119
|
+
params(
|
120
|
+
attr_val: T.untyped,
|
121
|
+
attr_type: T.any(Class, T::Types::Base),
|
122
|
+
).void
|
123
|
+
}
|
124
|
+
def validate_range_attr_types(attr_val, attr_type)
|
125
|
+
# Validate attribute types for range index attributes
|
126
|
+
if attr_val.is_a?(Redcord::RangeInterval)
|
127
|
+
validate_attr_type(
|
128
|
+
attr_val.min,
|
129
|
+
T.cast(T.nilable(attr_type), T::Types::Base),
|
130
|
+
)
|
131
|
+
validate_attr_type(
|
132
|
+
attr_val.max,
|
133
|
+
T.cast(T.nilable(attr_type), T::Types::Base),
|
134
|
+
)
|
135
|
+
else
|
136
|
+
validate_attr_type(attr_val, attr_type)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
sig {
|
141
|
+
params(
|
142
|
+
attr_val: T.untyped,
|
143
|
+
attr_type: T.any(Class, T::Types::Base),
|
144
|
+
).void
|
145
|
+
}
|
73
146
|
def validate_attr_type(attr_val, attr_type)
|
74
147
|
if (attr_type.is_a?(Class) && !attr_val.is_a?(attr_type)) ||
|
75
|
-
|
76
|
-
raise
|
77
|
-
|
148
|
+
(attr_type.is_a?(T::Types::Base) && !attr_type.valid?(attr_val))
|
149
|
+
raise(
|
150
|
+
Redcord::WrongAttributeType,
|
151
|
+
"Expected type #{attr_type}, got #{attr_val.class.name}",
|
78
152
|
)
|
79
153
|
end
|
80
154
|
end
|
81
155
|
|
82
|
-
sig {
|
156
|
+
sig {
|
157
|
+
params(
|
158
|
+
attribute: Symbol,
|
159
|
+
val: T.untyped,
|
160
|
+
).returns([T.untyped, T.untyped])
|
161
|
+
}
|
83
162
|
def encode_range_index_attr_val(attribute, val)
|
84
163
|
if val.is_a?(Redcord::RangeInterval)
|
85
|
-
# nil is treated as -inf and +inf. This is supported in Redis sorted
|
86
|
-
# so clients aren't required to know the highest and lowest scores
|
164
|
+
# nil is treated as -inf and +inf. This is supported in Redis sorted
|
165
|
+
# sets so clients aren't required to know the highest and lowest scores
|
166
|
+
# in a range
|
87
167
|
min_val = !val.min ? '-inf' : encode_attr_value(attribute, val.min)
|
88
168
|
max_val = !val.max ? '+inf' : encode_attr_value(attribute, val.max)
|
89
169
|
|
90
|
-
# In Redis, by default min and max is closed. You can prefix the score
|
91
|
-
# specify an open interval.
|
170
|
+
# In Redis, by default min and max is closed. You can prefix the score
|
171
|
+
# with '(' to specify an open interval.
|
92
172
|
min_val = val.min_exclusive ? '(' + min_val.to_s : min_val.to_s
|
93
173
|
max_val = val.max_exclusive ? '(' + max_val.to_s : max_val.to_s
|
94
|
-
|
174
|
+
[min_val, max_val]
|
95
175
|
else
|
96
|
-
# Equality queries for range indices are be passed to redis as a range
|
176
|
+
# Equality queries for range indices are be passed to redis as a range
|
177
|
+
# [val, val].
|
97
178
|
encoded_val = encode_attr_value(attribute, val)
|
98
179
|
[encoded_val, encoded_val]
|
99
180
|
end
|
@@ -104,24 +185,41 @@ module Redcord::Serializer
|
|
104
185
|
props[attr_key][:type_object]
|
105
186
|
end
|
106
187
|
|
107
|
-
sig {
|
188
|
+
sig {
|
189
|
+
params(
|
190
|
+
redis_hash: T::Hash[T.untyped, T.untyped],
|
191
|
+
id: String,
|
192
|
+
).returns(T.untyped)
|
193
|
+
}
|
108
194
|
def coerce_and_set_id(redis_hash, id)
|
109
|
-
# Coerce each serialized result returned from Redis back into Model
|
195
|
+
# Coerce each serialized result returned from Redis back into Model
|
196
|
+
# instance
|
110
197
|
instance = TypeCoerce.send(:[], self).new.from(from_redis_hash(redis_hash))
|
111
198
|
instance.send(:id=, id)
|
112
199
|
instance
|
113
200
|
end
|
201
|
+
|
114
202
|
sig { returns(String) }
|
115
203
|
def model_key
|
116
204
|
"Redcord:#{name}"
|
117
205
|
end
|
118
206
|
|
119
|
-
sig {
|
207
|
+
sig {
|
208
|
+
params(
|
209
|
+
args: T::Hash[T.any(String, Symbol), T.untyped],
|
210
|
+
).returns(T::Hash[Symbol, T.untyped])
|
211
|
+
}
|
120
212
|
def to_redis_hash(args)
|
121
|
-
args.map
|
213
|
+
args.map do |key, val|
|
214
|
+
[key.to_sym, encode_attr_value(key.to_sym, val)]
|
215
|
+
end.to_h
|
122
216
|
end
|
123
217
|
|
124
|
-
sig {
|
218
|
+
sig {
|
219
|
+
params(
|
220
|
+
args: T::Hash[T.untyped, T.untyped],
|
221
|
+
).returns(T::Hash[T.untyped, T.untyped])
|
222
|
+
}
|
125
223
|
def from_redis_hash(args)
|
126
224
|
args.map { |key, val| [key, decode_attr_value(key.to_sym, val)] }.to_h
|
127
225
|
end
|