redcord 0.0.2.alpha → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 679b0154429c95be05676a47608f0778c3d34e714d60eb5ec065863691fa9461
4
- data.tar.gz: 43847db0f9fd339c8a91db402b8b2da70ea4b3f874b6faf8a05d20f234cf0aea
3
+ metadata.gz: 7612a04876a98367ab0b8a6d94a0254d0d79aa564b3bd43e5119785f114d962c
4
+ data.tar.gz: 9b679659f7087bfd65d202a58be3f0836b1a7e8bc889f16d34e423340ce8c4ed
5
5
  SHA512:
6
- metadata.gz: b01c381b8e3436aaf0f4d2ebc8604bfd582c64663601a17044b0a29ca45c46e4e8ced22969ed5c53123af65808e1c4749cf262bec17e26468c1624c0023d2b9d
7
- data.tar.gz: cb4403b960c7f2e017143738edcbbfbe356e7e8c8e5b761d1f98482296541dcc2255b480d1285ce233d5add5c7e7e78fda29ab8f5fafd561f106a64e9e144460
6
+ metadata.gz: 44796213c268dfb63e59472c9b59470e3b01dd7a26b0acc63aa48e0f7ff537c25906957d3700b24639eb41d9526708898dc8ff82a4eeea193fc7ec1a3a5cfe43
7
+ data.tar.gz: d656f40756c62507113ac660429e9dc10751f40158c8768d47ccdb238ac2d392ccdfc055744ef68837608e7ed33f5673527178a9d0ef77a928d4cabd49da055f
@@ -1,9 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # typed: strict
2
- module Redcord
3
- end
4
4
 
5
5
  require 'sorbet-runtime'
6
6
 
7
+ module Redcord
8
+ extend T::Sig
9
+
10
+ @@configuration_blks = T.let(
11
+ [],
12
+ T::Array[T.proc.params(arg0: T.untyped).void],
13
+ )
14
+
15
+ sig {
16
+ params(
17
+ blk: T.proc.params(arg0: T.untyped).void,
18
+ ).void
19
+ }
20
+ def self.configure(&blk)
21
+ @@configuration_blks << blk
22
+ end
23
+
24
+ sig { void }
25
+ def self._after_initialize!
26
+ @@configuration_blks.each do |blk|
27
+ blk.call(Redcord::Base)
28
+ end
29
+
30
+ @@configuration_blks.clear
31
+ end
32
+ end
33
+
7
34
  require 'redcord/base'
8
35
  require 'redcord/migration'
9
36
  require 'redcord/migration/migrator'
@@ -45,22 +45,6 @@ module Redcord::TTL::ClassMethods
45
45
  include Redcord::Serializer::ClassMethods
46
46
  end
47
47
 
48
- module Redcord::ServerScripts
49
- include Kernel
50
-
51
- sig do
52
- params(
53
- sha: String,
54
- keys: T::Array[T.untyped],
55
- argv: T::Array[T.untyped],
56
- ).returns(T.untyped)
57
- end
58
- def evalsha(sha, keys: [], argv:[]); end
59
-
60
- sig { returns(T::Hash[Symbol, String]) }
61
- def redcord_server_script_shas; end
62
- end
63
-
64
48
  module Redcord::Actions::ClassMethods
65
49
  include Kernel
66
50
  include Redcord::RedisConnection::ClassMethods
@@ -26,27 +26,42 @@ module Redcord::Actions
26
26
 
27
27
  sig { params(args: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
28
28
  def create!(args)
29
- args[:created_at] = args[:updated_at] = Time.zone.now
30
- instance = TypeCoerce[self].new.from(args)
31
- id = redis.create_hash_returning_id(model_key, to_redis_hash(args))
32
- instance.send(:id=, id)
33
- instance
29
+ Redcord::Base.trace(
30
+ 'redcord_actions_class_methods_create!',
31
+ model_name: name,
32
+ ) do
33
+ args[:created_at] = args[:updated_at] = Time.zone.now
34
+ instance = TypeCoerce[self].new.from(args)
35
+ id = redis.create_hash_returning_id(model_key, to_redis_hash(args))
36
+ instance.send(:id=, id)
37
+ instance
38
+ end
34
39
  end
35
40
 
36
41
  sig { params(id: T.untyped).returns(T.untyped) }
37
42
  def find(id)
38
- instance_key = "#{model_key}:id:#{id}"
39
- args = redis.hgetall(instance_key)
40
- if args.empty?
41
- raise Redcord::RecordNotFound, "Couldn't find #{name} with 'id'=#{id}"
43
+ Redcord::Base.trace(
44
+ 'redcord_actions_class_methods_find',
45
+ model_name: name,
46
+ ) do
47
+ instance_key = "#{model_key}:id:#{id}"
48
+ args = redis.hgetall(instance_key)
49
+ if args.empty?
50
+ raise Redcord::RecordNotFound, "Couldn't find #{name} with 'id'=#{id}"
51
+ end
52
+
53
+ coerce_and_set_id(args, id)
42
54
  end
43
-
44
- coerce_and_set_id(args, id)
45
55
  end
46
56
 
47
57
  sig { params(args: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
48
58
  def find_by(args)
49
- where(args).to_a.first
59
+ Redcord::Base.trace(
60
+ 'redcord_actions_class_methods_find_by_args',
61
+ model_name: name,
62
+ ) do
63
+ where(args).to_a.first
64
+ end
50
65
  end
51
66
 
52
67
  sig { params(args: T::Hash[Symbol, T.untyped]).returns(Redcord::Relation) }
@@ -56,7 +71,12 @@ module Redcord::Actions
56
71
 
57
72
  sig { params(id: T.untyped).returns(T::Boolean) }
58
73
  def destroy(id)
59
- redis.delete_hash(model_key, id) == 1
74
+ Redcord::Base.trace(
75
+ 'redcord_actions_class_methods_destroy',
76
+ model_name: name,
77
+ ) do
78
+ redis.delete_hash(model_key, id) == 1
79
+ end
60
80
  end
61
81
  end
62
82
 
@@ -88,46 +108,61 @@ module Redcord::Actions
88
108
 
89
109
  sig { void }
90
110
  def save!
91
- self.updated_at = Time.zone.now
92
- _id = id
93
- if _id.nil?
94
- self.created_at = T.must(self.updated_at)
95
- _id = redis.create_hash_returning_id(
96
- self.class.model_key,
97
- self.class.to_redis_hash(serialize),
98
- )
99
- send(:id=, _id)
100
- else
101
- redis.update_hash(
102
- self.class.model_key,
103
- _id,
104
- self.class.to_redis_hash(serialize),
105
- )
111
+ Redcord::Base.trace(
112
+ 'redcord_actions_instance_methods_save!',
113
+ model_name: self.class.name,
114
+ ) do
115
+ self.updated_at = Time.zone.now
116
+ _id = id
117
+ if _id.nil?
118
+ self.created_at = T.must(self.updated_at)
119
+ _id = redis.create_hash_returning_id(
120
+ self.class.model_key,
121
+ self.class.to_redis_hash(serialize),
122
+ )
123
+ send(:id=, _id)
124
+ else
125
+ redis.update_hash(
126
+ self.class.model_key,
127
+ _id,
128
+ self.class.to_redis_hash(serialize),
129
+ )
130
+ end
106
131
  end
107
132
  end
108
133
 
109
134
  sig { params(args: T::Hash[Symbol, T.untyped]).void }
110
135
  def update!(args = {})
111
- _id = id
112
- if _id.nil?
113
- _set_args!(args)
114
- save!
115
- else
116
- args[:updated_at] = Time.zone.now
117
- _set_args!(args)
118
- redis.update_hash(
119
- self.class.model_key,
120
- _id,
121
- self.class.to_redis_hash(args),
122
- )
136
+ Redcord::Base.trace(
137
+ 'redcord_actions_instance_methods_update!',
138
+ model_name: self.class.name,
139
+ ) do
140
+ _id = id
141
+ if _id.nil?
142
+ _set_args!(args)
143
+ save!
144
+ else
145
+ args[:updated_at] = Time.zone.now
146
+ _set_args!(args)
147
+ redis.update_hash(
148
+ self.class.model_key,
149
+ _id,
150
+ self.class.to_redis_hash(args),
151
+ )
152
+ end
123
153
  end
124
154
  end
125
155
 
126
156
  sig { returns(T::Boolean) }
127
157
  def destroy
128
- return false if id.nil?
158
+ Redcord::Base.trace(
159
+ 'redcord_actions_instance_methods_destroy',
160
+ model_name: self.class.name,
161
+ ) do
162
+ return false if id.nil?
129
163
 
130
- self.class.destroy(T.must(id))
164
+ self.class.destroy(T.must(id))
165
+ end
131
166
  end
132
167
 
133
168
  sig { returns(String) }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+ #
1
3
  # typed: strict
2
4
  #
3
5
  # A Redis ORM API inspired by ActiveRecord:
@@ -11,6 +13,7 @@ require 'redcord/configurations'
11
13
  require 'redcord/logger'
12
14
  require 'redcord/redis_connection'
13
15
  require 'redcord/serializer'
16
+ require 'redcord/tracer'
14
17
 
15
18
  module Redcord::Base
16
19
  extend T::Sig
@@ -23,6 +26,7 @@ module Redcord::Base
23
26
  include Redcord::Configurations
24
27
  include Redcord::Logger
25
28
  include Redcord::RedisConnection
29
+ include Redcord::Tracer
26
30
 
27
31
  abstract!
28
32
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+ #
1
3
  # typed: strict
2
4
  #
3
5
  # This allows us to configure Redis connections for Redcord. Redis
@@ -40,6 +42,8 @@
40
42
  # ```
41
43
  #
42
44
  require 'redcord/redis_connection'
45
+ require 'redcord/tracer'
46
+
43
47
  module Redcord::Configurations
44
48
  extend T::Sig
45
49
  extend T::Helpers
@@ -12,7 +12,7 @@ module Redcord::Logger
12
12
  module ClassMethods
13
13
  extend T::Sig
14
14
 
15
- @@logger = T.let(Rails.logger, T.untyped)
15
+ @@logger = T.let(nil, T.untyped)
16
16
 
17
17
  sig { returns(T.untyped) }
18
18
  def logger
@@ -1,18 +1,147 @@
1
1
  # typed: strict
2
2
  require 'redis'
3
- require 'redcord/server_scripts'
4
3
 
4
+ # TODO: Rename Redcord::PreparedRedis -> Redcord::Redis
5
5
  class Redcord::PreparedRedis < Redis
6
6
  extend T::Sig
7
- include Redcord::ServerScripts
7
+
8
+ sig do
9
+ params(
10
+ key: T.any(String, Symbol),
11
+ args: T::Hash[T.untyped, T.untyped],
12
+ ).returns(Integer)
13
+ end
14
+ def create_hash_returning_id(key, args)
15
+ Redcord::Base.trace(
16
+ 'redcord_redis_create_hash_returning_id',
17
+ model_name: key,
18
+ ) do
19
+ evalsha(
20
+ self.class.server_script_shas[:create_hash_returning_id],
21
+ keys: [key],
22
+ argv: args.to_a.flatten,
23
+ ).to_i
24
+ end
25
+ end
26
+
27
+ sig do
28
+ params(
29
+ model: String,
30
+ id: Integer,
31
+ args: T::Hash[T.untyped, T.untyped],
32
+ ).void
33
+ end
34
+ def update_hash(model, id, args)
35
+ Redcord::Base.trace(
36
+ 'redcord_redis_update_hash',
37
+ model_name: model,
38
+ ) do
39
+ evalsha(
40
+ self.class.server_script_shas[:update_hash],
41
+ keys: [model, id],
42
+ argv: args.to_a.flatten,
43
+ )
44
+ end
45
+ end
46
+
47
+ sig do
48
+ params(
49
+ model: String,
50
+ id: Integer
51
+ ).returns(Integer)
52
+ end
53
+ def delete_hash(model, id)
54
+ Redcord::Base.trace(
55
+ 'redcord_redis_delete_hash',
56
+ model_name: model,
57
+ ) do
58
+ evalsha(
59
+ self.class.server_script_shas[:delete_hash],
60
+ keys: [model, id]
61
+ )
62
+ end
63
+ end
64
+
65
+ sig do
66
+ params(
67
+ model: String,
68
+ query_conditions: T::Hash[T.untyped, T.untyped],
69
+ select_attrs: T::Set[Symbol]
70
+ ).returns(T::Hash[Integer, T::Hash[T.untyped, T.untyped]])
71
+ end
72
+ def find_by_attr(model, query_conditions, select_attrs=Set.new)
73
+ Redcord::Base.trace(
74
+ 'redcord_redis_find_by_attr',
75
+ model_name: model,
76
+ ) do
77
+ res = evalsha(
78
+ self.class.server_script_shas[:find_by_attr],
79
+ keys: [model] + query_conditions.to_a.flatten,
80
+ argv: select_attrs.to_a.flatten
81
+ )
82
+ # The Lua script will return this as a flattened array.
83
+ # Convert the result into a hash of {id -> model hash}
84
+ res_hash = res.each_slice(2)
85
+ res_hash.map { |key, val| [key.to_i, val.each_slice(2).to_h] }.to_h
86
+ end
87
+ end
88
+
89
+ sig do
90
+ params(
91
+ model: String,
92
+ query_conditions: T::Hash[T.untyped, T.untyped]
93
+ ).returns(Integer)
94
+ end
95
+ def find_by_attr_count(model, query_conditions)
96
+ Redcord::Base.trace(
97
+ 'redcord_redis_find_by_attr_count',
98
+ model_name: model,
99
+ ) do
100
+ evalsha(
101
+ self.class.server_script_shas[:find_by_attr_count],
102
+ keys: [model] + query_conditions.to_a.flatten,
103
+ )
104
+ end
105
+ end
106
+
107
+ sig { void }
108
+ def load_server_scripts!
109
+ script_names = Dir[File.join(
110
+ __dir__,
111
+ 'server_scripts/*.lua',
112
+ )].map do |filename|
113
+ # lib/redcord/server_scripts/find_by_attr.erb.lua -> find_by_attr
114
+ T.must(filename.split('/').last).split('.').first&.to_sym
115
+ end
116
+
117
+ res = pipelined do
118
+ script_names.each do |script_name|
119
+ script(
120
+ :load,
121
+ Redcord::LuaScriptReader.read_lua_script(script_name.to_s),
122
+ )
123
+ end
124
+ end
125
+
126
+ if self.class.class_variable_get(:@@server_script_shas).nil?
127
+ self.class.class_variable_set(
128
+ :@@server_script_shas,
129
+ script_names.zip(res).to_h
130
+ )
131
+ end
132
+ end
133
+
134
+ @@server_script_shas = T.let(nil, T.nilable(T::Hash[Symbol, String]))
8
135
 
9
136
  sig { returns(T::Hash[Symbol, String]) }
10
- def redcord_server_script_shas
11
- instance_variable_get(:@_redcord_server_script_shas)
137
+ def self.server_script_shas
138
+ T.must(@@server_script_shas)
12
139
  end
13
140
 
14
- sig { params(shas: T::Hash[Symbol, String]).void }
15
- def redcord_server_script_shas=(shas)
16
- instance_variable_set(:@_redcord_server_script_shas, shas)
141
+ sig { void }
142
+ def self.load_server_scripts!
143
+ Redcord::Base.configurations[Rails.env].each do |_, config|
144
+ new(**(config.symbolize_keys)).load_server_scripts!
145
+ end
17
146
  end
18
147
  end
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # typed: strict
4
+
2
5
  require 'rails'
6
+ require 'yaml'
3
7
 
4
8
  class Redcord::Railtie < Rails::Railtie
5
9
  railtie_name 'redcord'
@@ -13,4 +17,19 @@ class Redcord::Railtie < Rails::Railtie
13
17
  config.before_configuration do
14
18
  require 'redcord/base'
15
19
  end
20
+
21
+ config.after_initialize do
22
+ Redcord::Base.logger = Rails.logger
23
+
24
+ config_file = 'config/redcord.yml'
25
+
26
+ if File.file?(config_file)
27
+ Redcord::Base.configurations = YAML.load(
28
+ ERB.new(File.read(config_file)).result
29
+ )
30
+ end
31
+
32
+ Redcord::PreparedRedis.load_server_scripts!
33
+ Redcord._after_initialize!
34
+ end
16
35
  end
@@ -71,17 +71,6 @@ module Redcord::RedisConnection
71
71
  end
72
72
  end
73
73
 
74
- script_names = Redcord::ServerScripts.instance_methods
75
- res = client.pipelined do
76
- script_names.each do |script_name|
77
- client.script(
78
- :load,
79
- Redcord::LuaScriptReader.read_lua_script(script_name.to_s),
80
- )
81
- end
82
- end
83
-
84
- client.redcord_server_script_shas = script_names.zip(res).to_h
85
74
  client
86
75
  end
87
76
  end
@@ -51,14 +51,19 @@ class Redcord::Relation
51
51
  ).returns(T.any(Redcord::Relation, T::Array[T.untyped]))
52
52
  end
53
53
  def select(*args, &blk)
54
- if block_given?
55
- return execute_query.select do |*item|
56
- blk.call(*item)
54
+ Redcord::Base.trace(
55
+ 'redcord_relation_select',
56
+ model_name: model.name,
57
+ ) do
58
+ if block_given?
59
+ return execute_query.select do |*item|
60
+ blk.call(*item)
61
+ end
57
62
  end
58
- end
59
63
 
60
- select_attrs.merge(args)
61
- self
64
+ select_attrs.merge(args)
65
+ self
66
+ end
62
67
  end
63
68
 
64
69
  sig { returns(Integer) }
@@ -134,25 +139,30 @@ class Redcord::Relation
134
139
 
135
140
  sig { returns(T::Array[T.untyped]) }
136
141
  def execute_query
137
- if !select_attrs.empty?
138
- res_hash = redis.find_by_attr(
139
- model.model_key,
140
- query_conditions,
141
- select_attrs,
142
- )
143
-
144
- res_hash.map do |id, args|
145
- model.from_redis_hash(args).map do |k, v|
146
- [k.to_sym, TypeCoerce[model.get_attr_type(k.to_sym)].new.from(v)]
147
- end.to_h.merge(id: id)
142
+ Redcord::Base.trace(
143
+ 'redcord_relation_execute_query',
144
+ model_name: model.name,
145
+ ) do
146
+ if !select_attrs.empty?
147
+ res_hash = redis.find_by_attr(
148
+ model.model_key,
149
+ query_conditions,
150
+ select_attrs,
151
+ )
152
+
153
+ res_hash.map do |id, args|
154
+ model.from_redis_hash(args).map do |k, v|
155
+ [k.to_sym, TypeCoerce[model.get_attr_type(k.to_sym)].new.from(v)]
156
+ end.to_h.merge(id: id)
157
+ end
158
+ else
159
+ res_hash = redis.find_by_attr(
160
+ model.model_key,
161
+ query_conditions,
162
+ )
163
+
164
+ res_hash.map { |id, args| model.coerce_and_set_id(args, id) }
148
165
  end
149
- else
150
- res_hash = redis.find_by_attr(
151
- model.model_key,
152
- query_conditions,
153
- )
154
-
155
- res_hash.map { |id, args| model.coerce_and_set_id(args, id) }
156
166
  end
157
167
  end
158
168
 
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: strict
4
+
5
+ module Redcord::Tracer
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ sig { params(klass: Module).void }
10
+ def self.included(klass)
11
+ klass.extend(ClassMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+ include Kernel
16
+
17
+ extend T::Sig
18
+
19
+ @@tracer = T.let(nil, T.untyped)
20
+
21
+ sig {
22
+ params(
23
+ span_name: String,
24
+ model_name: String,
25
+ tags: T::Array[String],
26
+ blk: T.proc.returns(T.untyped),
27
+ ).returns(T.untyped)
28
+ }
29
+ def trace(span_name, model_name:, tags: [], &blk)
30
+ return blk.call if @@tracer.nil?
31
+
32
+ @@tracer.call.trace(
33
+ span_name,
34
+ resource: model_name,
35
+ service: 'redcord',
36
+ tags: tags,
37
+ &blk
38
+ )
39
+ end
40
+
41
+ sig { params(blk: T.proc.returns(T.untyped)).void }
42
+ def tracer(&blk)
43
+ @@tracer = blk
44
+ end
45
+ end
46
+
47
+ mixes_in_class_methods(ClassMethods)
48
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redcord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2.alpha
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chan Zuckerberg Initiative
@@ -174,7 +174,6 @@ files:
174
174
  - lib/redcord/redis_connection.rb
175
175
  - lib/redcord/relation.rb
176
176
  - lib/redcord/serializer.rb
177
- - lib/redcord/server_scripts.rb
178
177
  - lib/redcord/server_scripts/create_hash_returning_id.erb.lua
179
178
  - lib/redcord/server_scripts/delete_hash.erb.lua
180
179
  - lib/redcord/server_scripts/find_by_attr.erb.lua
@@ -184,6 +183,7 @@ files:
184
183
  - lib/redcord/server_scripts/shared/query_helper_methods.erb.lua
185
184
  - lib/redcord/server_scripts/update_hash.erb.lua
186
185
  - lib/redcord/tasks/redis.rake
186
+ - lib/redcord/tracer.rb
187
187
  homepage: https://github.com/chanzuckerberg/redis-record
188
188
  licenses:
189
189
  - MIT
@@ -199,9 +199,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
199
199
  version: 2.5.0
200
200
  required_rubygems_version: !ruby/object:Gem::Requirement
201
201
  requirements:
202
- - - ">"
202
+ - - ">="
203
203
  - !ruby/object:Gem::Version
204
- version: 1.3.1
204
+ version: '0'
205
205
  requirements: []
206
206
  rubyforge_project:
207
207
  rubygems_version: 2.7.6.2
@@ -1,78 +0,0 @@
1
- # typed: strict
2
- module Redcord::ServerScripts
3
- extend T::Sig
4
-
5
- sig do
6
- params(
7
- key: T.any(String, Symbol),
8
- args: T::Hash[T.untyped, T.untyped],
9
- ).returns(Integer)
10
- end
11
- def create_hash_returning_id(key, args)
12
- evalsha(
13
- T.must(redcord_server_script_shas[:create_hash_returning_id]),
14
- keys: [key],
15
- argv: args.to_a.flatten,
16
- ).to_i
17
- end
18
-
19
- sig do
20
- params(
21
- model: String,
22
- id: Integer,
23
- args: T::Hash[T.untyped, T.untyped],
24
- ).void
25
- end
26
- def update_hash(model, id, args)
27
- evalsha(
28
- T.must(redcord_server_script_shas[:update_hash]),
29
- keys: [model, id],
30
- argv: args.to_a.flatten,
31
- )
32
- end
33
-
34
- sig do
35
- params(
36
- model: String,
37
- id: Integer
38
- ).returns(Integer)
39
- end
40
- def delete_hash(model, id)
41
- evalsha(
42
- T.must(redcord_server_script_shas[:delete_hash]),
43
- keys: [model, id]
44
- )
45
- end
46
-
47
- sig do
48
- params(
49
- model: String,
50
- query_conditions: T::Hash[T.untyped, T.untyped],
51
- select_attrs: T::Set[Symbol]
52
- ).returns(T::Hash[Integer, T::Hash[T.untyped, T.untyped]])
53
- end
54
- def find_by_attr(model, query_conditions, select_attrs=Set.new)
55
- res = evalsha(
56
- T.must(redcord_server_script_shas[:find_by_attr]),
57
- keys: [model] + query_conditions.to_a.flatten,
58
- argv: select_attrs.to_a.flatten
59
- )
60
- # The Lua script will return this as a flattened array.
61
- # Convert the result into a hash of {id -> model hash}
62
- res_hash = res.each_slice(2)
63
- res_hash.map { |key, val| [key.to_i, val.each_slice(2).to_h] }.to_h
64
- end
65
-
66
- sig do
67
- params(
68
- model: String,
69
- query_conditions: T::Hash[T.untyped, T.untyped]
70
- ).returns(Integer)
71
- end
72
- def find_by_attr_count(model, query_conditions)
73
- evalsha(
74
- T.must(redcord_server_script_shas[:find_by_attr_count]),
75
- keys: [model] + query_conditions.to_a.flatten,
76
- )
77
- end
78
- end