redcord 0.0.2.alpha → 0.0.3

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 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