kirei 0.3.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -7
- data/kirei.gemspec +1 -0
- data/lib/cli/commands/new_app/execute.rb +1 -1
- data/lib/cli/commands/new_app/files/app.rb +9 -2
- data/lib/cli/commands/new_app/files/rakefile.rb +3 -0
- data/lib/cli/commands/new_app/files/routes.rb +2 -2
- data/lib/cli/commands/start.rb +1 -1
- data/lib/kirei/app.rb +3 -0
- data/lib/kirei/config.rb +4 -1
- data/lib/kirei/errors/json_api_error.rb +25 -0
- data/lib/kirei/errors/json_api_error_source.rb +12 -0
- data/lib/kirei/logging/level.rb +33 -0
- data/lib/kirei/logging/logger.rb +198 -0
- data/lib/kirei/logging/metric.rb +40 -0
- data/lib/kirei/model/base_class_interface.rb +55 -0
- data/lib/kirei/model/class_methods.rb +157 -0
- data/lib/kirei/model/human_id_generator.rb +40 -0
- data/lib/kirei/model.rb +4 -175
- data/lib/kirei/routing/base.rb +53 -13
- data/lib/kirei/routing/route.rb +13 -0
- data/lib/kirei/routing/router.rb +1 -31
- data/lib/kirei/routing/verb.rb +37 -0
- data/lib/kirei/services/result.rb +53 -0
- data/lib/kirei/services/runner.rb +54 -0
- data/lib/kirei/version.rb +1 -1
- data/lib/kirei.rb +5 -2
- data/lib/tasks/routes.rake +26 -0
- data/sorbet/rbi/shims/ruby.rbi +15 -0
- metadata +30 -3
- data/lib/kirei/logger.rb +0 -196
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86e3ce57ba0d801b5279632d302837a42195565c3daa66a2b5fbd6adb31f0176
|
4
|
+
data.tar.gz: a3ba957501c2603cc8cfba22282ab0c281ccf8227f99fedad78af0c980f3edd3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '078c2a848f346f8e25c0b9fcacc72d0d2e3b8f82620df2cffe4fee683e9d44aba43d3de635dd918449369617aa97362d2f66adba26d52c625565ff614eafe5d9'
|
7
|
+
data.tar.gz: a57e28a79ff8fe6bf47060550d11521b14112f22e15088a365bf7965ef0caa1a9f56a80e55571477cea8807c63775513fdd04eb63bda355f677ce5aba95d032d
|
data/README.md
CHANGED
@@ -82,13 +82,13 @@ Delete keeps the original object intact. Returns `true` if the record was delete
|
|
82
82
|
success = user.delete # => T::Boolean
|
83
83
|
|
84
84
|
# or delete by any query:
|
85
|
-
User.
|
85
|
+
User.query.where('...').delete # => Integer, number of deleted records
|
86
86
|
```
|
87
87
|
|
88
88
|
To build more complex queries, Sequel can be used directly:
|
89
89
|
|
90
90
|
```ruby
|
91
|
-
query = User.
|
91
|
+
query = User.query.where({ name: 'John' })
|
92
92
|
query = query.where('...') # "query" is a 'Sequel::Dataset' that you can chain as you like
|
93
93
|
query = query.limit(10)
|
94
94
|
|
@@ -154,14 +154,14 @@ Define routes anywhere in your app; by convention, they are defined in `config/r
|
|
154
154
|
module Kirei::Routing
|
155
155
|
Router.add_routes(
|
156
156
|
[
|
157
|
-
|
158
|
-
verb:
|
157
|
+
Route.new(
|
158
|
+
verb: Verb::GET,
|
159
159
|
path: "/livez",
|
160
160
|
controller: Controllers::Health,
|
161
161
|
action: "livez",
|
162
162
|
),
|
163
|
-
|
164
|
-
verb:
|
163
|
+
Route.new(
|
164
|
+
verb: Verb::GET,
|
165
165
|
path: "/airports",
|
166
166
|
controller: Controllers::Airports,
|
167
167
|
action: "index",
|
@@ -182,7 +182,11 @@ module Controllers
|
|
182
182
|
|
183
183
|
sig { returns(T.anything) }
|
184
184
|
def index
|
185
|
-
|
185
|
+
search = T.let(params.fetch("q", nil), T.nilable(String))
|
186
|
+
|
187
|
+
airports = Kirei::Services::Runner.call("Airports::Filter") do
|
188
|
+
Airports::Filter.call(search) # T::Array[Airport]
|
189
|
+
end
|
186
190
|
|
187
191
|
# or use a serializer
|
188
192
|
data = Oj.dump(airports.map(&:serialize))
|
@@ -193,6 +197,35 @@ module Controllers
|
|
193
197
|
end
|
194
198
|
```
|
195
199
|
|
200
|
+
Services can be PORO. You can wrap an execution in `Kirei::Services::Runner` which will emit a standardized logline and track its execution time.
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
module Airports
|
204
|
+
class Filter
|
205
|
+
extend T::Sig
|
206
|
+
|
207
|
+
sig do
|
208
|
+
params(
|
209
|
+
search: T.nilable(String),
|
210
|
+
).returns(T::Array[Airport])
|
211
|
+
end
|
212
|
+
def self.call(search)
|
213
|
+
return Airport.all if search.nil?
|
214
|
+
|
215
|
+
#
|
216
|
+
# SELECT *
|
217
|
+
# FROM "airports"
|
218
|
+
# WHERE (("name" ILIKE 'xx%') OR ("id" ILIKE 'xx%'))
|
219
|
+
#
|
220
|
+
query = Airport.query.where(Sequel.ilike(:name, "#{search}%"))
|
221
|
+
query = query.or(Sequel.ilike(:id, "#{search}%"))
|
222
|
+
|
223
|
+
Airport.resolve(query)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
196
229
|
## Contributions
|
197
230
|
|
198
231
|
We welcome contributions from the community. Before starting work on a major feature, please get in touch with us either via email or by opening an issue on GitHub. "Major feature" means anything that changes user-facing features or significant changes to the codebase itself.
|
data/kirei.gemspec
CHANGED
@@ -46,6 +46,7 @@ Gem::Specification.new do |spec|
|
|
46
46
|
# Utilities
|
47
47
|
spec.add_dependency "oj", "~> 3.0"
|
48
48
|
spec.add_dependency "sorbet-runtime", "~> 0.5"
|
49
|
+
spec.add_dependency "statsd-instrument", "~> 3.0"
|
49
50
|
spec.add_dependency "tzinfo-data", "~> 1.0" # for containerized environments, e.g. on AWS ECS
|
50
51
|
spec.add_dependency "zeitwerk", "~> 2.5"
|
51
52
|
|
@@ -31,8 +31,15 @@ module Cli
|
|
31
31
|
# Fourth: load all application code
|
32
32
|
loader = Zeitwerk::Loader.new
|
33
33
|
loader.tag = File.basename(__FILE__, ".rb")
|
34
|
-
|
35
|
-
|
34
|
+
[
|
35
|
+
"/app",
|
36
|
+
"/app/models",
|
37
|
+
"/app/services",
|
38
|
+
].each do |root_namespace|
|
39
|
+
# a root namespace skips the auto-infered module for this folder
|
40
|
+
# so we don't have to write e.g. `Models::` or `Services::`
|
41
|
+
loader.push_dir("\#{File.dirname(__FILE__)}\#{root_namespace}")
|
42
|
+
end
|
36
43
|
loader.setup
|
37
44
|
|
38
45
|
# Fifth: load configs
|
data/lib/cli/commands/start.rb
CHANGED
@@ -13,7 +13,7 @@ module Cli
|
|
13
13
|
app_name = app_name.split("_").map(&:capitalize).join if app_name.include?("_")
|
14
14
|
NewApp::Execute.call(app_name: app_name)
|
15
15
|
else
|
16
|
-
Kirei::Logger.logger.info("Unknown command")
|
16
|
+
Kirei::Logging::Logger.logger.info("Unknown command")
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/kirei/app.rb
CHANGED
data/lib/kirei/config.rb
CHANGED
@@ -17,7 +17,10 @@ module Kirei
|
|
17
17
|
|
18
18
|
prop :logger, ::Logger, factory: -> { ::Logger.new($stdout) }
|
19
19
|
prop :log_transformer, T.nilable(T.proc.params(msg: T::Hash[Symbol, T.untyped]).returns(T::Array[String]))
|
20
|
-
prop :log_default_metadata, T::Hash[
|
20
|
+
prop :log_default_metadata, T::Hash[String, T.untyped], default: {}
|
21
|
+
prop :log_level, Kirei::Logging::Level, default: Kirei::Logging::Level::INFO
|
22
|
+
|
23
|
+
prop :metric_default_tags, T::Hash[String, T.untyped], default: {}
|
21
24
|
|
22
25
|
# dup to allow the user to extend the existing list of sensitive keys
|
23
26
|
prop :sensitive_keys, T::Array[Regexp], factory: -> { SENSITIVE_KEYS.dup }
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Errors
|
6
|
+
#
|
7
|
+
# https://jsonapi.org/format/#errors
|
8
|
+
# Error objects MUST be returned as an array keyed by errors in the top level of a JSON:API document.
|
9
|
+
#
|
10
|
+
class JsonApiError < T::Struct
|
11
|
+
#
|
12
|
+
# An application-specific error code, expressed as a string value.
|
13
|
+
#
|
14
|
+
const :code, String
|
15
|
+
|
16
|
+
#
|
17
|
+
# A human-readable explanation specific to this occurrence of the problem.
|
18
|
+
# Like title, this field's value can be localized.
|
19
|
+
#
|
20
|
+
const :detail, T.nilable(String)
|
21
|
+
|
22
|
+
const :source, T.nilable(JsonApiErrorSource)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Logging
|
6
|
+
class Level < T::Enum
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
enums do
|
10
|
+
UNKNOWN = new(5) # An unknown message that should always be logged.
|
11
|
+
FATAL = new(4) # An unhandleable error that results in a program crash.
|
12
|
+
ERROR = new(3) # A handleable error condition.
|
13
|
+
WARN = new(2) # A warning.
|
14
|
+
INFO = new(1) # Generic (useful) information about system operation.
|
15
|
+
DEBUG = new(0) # Low-level information for developers.
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { returns(String) }
|
19
|
+
def to_human
|
20
|
+
case self
|
21
|
+
when UNKNOWN then "UNKNOWN"
|
22
|
+
when FATAL then "FATAL"
|
23
|
+
when ERROR then "ERROR"
|
24
|
+
when WARN then "WARN"
|
25
|
+
when INFO then "INFO"
|
26
|
+
when DEBUG then "DEBUG"
|
27
|
+
else
|
28
|
+
T.absurd(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
# rubocop:disable Metrics
|
6
|
+
|
7
|
+
#
|
8
|
+
# Example Usage:
|
9
|
+
#
|
10
|
+
# Kirei::Logging::Logger.call(
|
11
|
+
# level: Kirei::Logging::Level::INFO,
|
12
|
+
# label: "Request started",
|
13
|
+
# meta: {
|
14
|
+
# key: "value",
|
15
|
+
# },
|
16
|
+
# )
|
17
|
+
#
|
18
|
+
# You can define a custom log transformer to transform the logline:
|
19
|
+
#
|
20
|
+
# Kirei::App.config.log_transformer = Proc.new { _1 }
|
21
|
+
#
|
22
|
+
# By default, "meta" is flattened, and sensitive values are masked using sane defaults that you
|
23
|
+
# can finetune via `Kirei::App.config.sensitive_keys`.
|
24
|
+
#
|
25
|
+
# You can also build on top of the provided log transformer:
|
26
|
+
#
|
27
|
+
# Kirei::App.config.log_transformer = Proc.new do |meta|
|
28
|
+
# flattened_meta = Kirei::Logging::Logger.flatten_hash_and_mask_sensitive_values(meta)
|
29
|
+
# # Do something with the flattened meta
|
30
|
+
# flattened_meta.map { _1.to_json }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# NOTE:
|
34
|
+
# * The log transformer must return an array of strings to allow emitting multiple lines per log event.
|
35
|
+
# * Whenever possible, key names follow OpenTelemetry Semantic Conventions, https://opentelemetry.io/docs/concepts/semantic-conventions/
|
36
|
+
#
|
37
|
+
module Logging
|
38
|
+
class Logger
|
39
|
+
extend T::Sig
|
40
|
+
|
41
|
+
FILTERED = "[FILTERED]"
|
42
|
+
|
43
|
+
@instance = T.let(nil, T.nilable(Kirei::Logging::Logger))
|
44
|
+
|
45
|
+
sig { void }
|
46
|
+
def initialize
|
47
|
+
super
|
48
|
+
@queue = T.let(Thread::Queue.new, Thread::Queue)
|
49
|
+
@thread = T.let(start_logging_thread, Thread)
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(Kirei::Logging::Logger) }
|
53
|
+
def self.instance
|
54
|
+
@instance ||= new
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { returns(::Logger) }
|
58
|
+
def self.logger
|
59
|
+
return @logger unless @logger.nil?
|
60
|
+
|
61
|
+
@logger = T.let(nil, T.nilable(::Logger))
|
62
|
+
@logger ||= ::Logger.new($stdout)
|
63
|
+
|
64
|
+
# we want the logline to be parseable to JSON
|
65
|
+
@logger.formatter = proc do |_severity, _datetime, _progname, msg|
|
66
|
+
"#{msg}\n"
|
67
|
+
end
|
68
|
+
|
69
|
+
@logger
|
70
|
+
end
|
71
|
+
|
72
|
+
sig do
|
73
|
+
params(
|
74
|
+
level: Logging::Level,
|
75
|
+
label: String,
|
76
|
+
meta: T::Hash[String, T.untyped],
|
77
|
+
).void
|
78
|
+
end
|
79
|
+
def self.call(level:, label:, meta: {})
|
80
|
+
return if ENV["NO_LOGS"] == "true"
|
81
|
+
return if level.serialize < App.config.log_level.serialize
|
82
|
+
|
83
|
+
# must extract data from current thread before passing this down to the logging thread
|
84
|
+
meta["enduser.id"] ||= Thread.current[:enduser_id] # OpenTelemetry::SemanticConventions::Trace::ENDUSER_ID
|
85
|
+
meta["service.instance.id"] ||= Thread.current[:request_id] # OpenTelemetry::SemanticConventions::Resource::SERVICE_INSTANCE_ID
|
86
|
+
|
87
|
+
instance.call(level: level, label: label, meta: meta)
|
88
|
+
end
|
89
|
+
|
90
|
+
sig do
|
91
|
+
params(
|
92
|
+
level: Logging::Level,
|
93
|
+
label: String,
|
94
|
+
meta: T::Hash[String, T.untyped],
|
95
|
+
).void
|
96
|
+
end
|
97
|
+
def call(level:, label:, meta: {})
|
98
|
+
Kirei::App.config.log_default_metadata.each_pair do |key, value|
|
99
|
+
meta[key] ||= value
|
100
|
+
end
|
101
|
+
|
102
|
+
meta["service.name"] ||= Kirei::App.config.app_name # OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME
|
103
|
+
meta["service.version"] = Kirei::App.version # OpenTelemetry::SemanticConventions::Resource::SERVICE_VERSION
|
104
|
+
meta["timestamp"] ||= Time.now.utc.iso8601
|
105
|
+
meta["level"] ||= level.to_human
|
106
|
+
meta["label"] ||= label
|
107
|
+
|
108
|
+
@queue << meta
|
109
|
+
end
|
110
|
+
|
111
|
+
sig { returns(Thread) }
|
112
|
+
def start_logging_thread
|
113
|
+
Thread.new do
|
114
|
+
Kernel.loop do
|
115
|
+
log_data = T.let(@queue.pop, T::Hash[Symbol, T.untyped])
|
116
|
+
log_transformer = App.config.log_transformer
|
117
|
+
|
118
|
+
loglines = if log_transformer
|
119
|
+
log_transformer.call(log_data)
|
120
|
+
else
|
121
|
+
[Oj.dump(
|
122
|
+
Kirei::Logging::Logger.flatten_hash_and_mask_sensitive_values(log_data),
|
123
|
+
Kirei::OJ_OPTIONS,
|
124
|
+
)]
|
125
|
+
end
|
126
|
+
|
127
|
+
loglines.each do |logline|
|
128
|
+
logline = "\n#{logline}\n" if Kirei::App.environment == "development"
|
129
|
+
Kirei::Logging::Logger.logger.unknown(logline)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# rubocop:disable Naming/MethodParameterName
|
136
|
+
sig do
|
137
|
+
params(
|
138
|
+
k: String,
|
139
|
+
v: String,
|
140
|
+
).returns(String)
|
141
|
+
end
|
142
|
+
def self.mask(k, v)
|
143
|
+
App.config.sensitive_keys.any? { k.match?(_1) } ? FILTERED : v
|
144
|
+
end
|
145
|
+
# rubocop:enable Naming/MethodParameterName
|
146
|
+
|
147
|
+
sig do
|
148
|
+
params(
|
149
|
+
hash: T::Hash[T.any(Symbol, String), T.untyped],
|
150
|
+
prefix: String,
|
151
|
+
).returns(T::Hash[String, T.untyped])
|
152
|
+
end
|
153
|
+
def self.flatten_hash_and_mask_sensitive_values(hash, prefix = "")
|
154
|
+
result = T.let({}, T::Hash[String, T.untyped])
|
155
|
+
Kirei::Helpers.deep_stringify_keys!(hash)
|
156
|
+
hash = T.cast(hash, T::Hash[String, T.untyped])
|
157
|
+
|
158
|
+
hash.each do |key, value|
|
159
|
+
new_prefix = Kirei::Helpers.blank?(prefix) ? key : "#{prefix}.#{key}"
|
160
|
+
|
161
|
+
case value
|
162
|
+
when Hash
|
163
|
+
# Some libraries have a custom Hash class that inhert from Hash, but act differently, e.g. OmniAuth::AuthHash.
|
164
|
+
# This results in `transform_keys` being available but without any effect.
|
165
|
+
value = value.to_h if value.class != Hash
|
166
|
+
result.merge!(flatten_hash_and_mask_sensitive_values(value.transform_keys(&:to_s), new_prefix))
|
167
|
+
when Array
|
168
|
+
value.each_with_index do |element, index|
|
169
|
+
if element.is_a?(Hash) || element.is_a?(Array)
|
170
|
+
result.merge!(flatten_hash_and_mask_sensitive_values({ index => element }, new_prefix))
|
171
|
+
else
|
172
|
+
result["#{new_prefix}.#{index}"] = element.is_a?(String) ? mask(key, element) : element
|
173
|
+
end
|
174
|
+
end
|
175
|
+
when String then result[new_prefix] = mask(key, value)
|
176
|
+
when Numeric, FalseClass, TrueClass, NilClass then result[new_prefix] = value
|
177
|
+
else
|
178
|
+
if value.respond_to?(:serialize)
|
179
|
+
serialized_value = value.serialize
|
180
|
+
if serialized_value.is_a?(Hash)
|
181
|
+
result.merge!(
|
182
|
+
flatten_hash_and_mask_sensitive_values(serialized_value.transform_keys(&:to_s), new_prefix),
|
183
|
+
)
|
184
|
+
else
|
185
|
+
result[new_prefix] = serialized_value&.to_s
|
186
|
+
end
|
187
|
+
else
|
188
|
+
result[new_prefix] = value&.to_s
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
result
|
194
|
+
end
|
195
|
+
end
|
196
|
+
# rubocop:enable Metrics
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Kirei
|
5
|
+
module Logging
|
6
|
+
class Metric
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig do
|
10
|
+
params(
|
11
|
+
metric_name: String,
|
12
|
+
value: Integer,
|
13
|
+
tags: T::Hash[String, T.untyped],
|
14
|
+
).void
|
15
|
+
end
|
16
|
+
def self.call(metric_name, value = 1, tags: {})
|
17
|
+
return if ENV["NO_METRICS"] == "true"
|
18
|
+
|
19
|
+
inject_defaults(tags)
|
20
|
+
|
21
|
+
# Do not `compact_blank` tags, since one might want to track empty strings/"false"/NULLs.
|
22
|
+
# NOT having any tag doesn't tell the user if the tag was empty or not set at all.
|
23
|
+
StatsD.increment(metric_name, value, tags: tags)
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(tags: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]) }
|
27
|
+
def self.inject_defaults(tags)
|
28
|
+
App.config.metric_default_tags.each_pair do |key, default_value|
|
29
|
+
tags[key] ||= default_value
|
30
|
+
end
|
31
|
+
|
32
|
+
tags["enduser.id"] ||= Thread.current[:enduser_id]
|
33
|
+
tags["service.name"] ||= Kirei::App.config.app_name # OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME
|
34
|
+
tags["service.version"] = Kirei::App.version # OpenTelemetry::SemanticConventions::Resource::SERVICE_VERSION
|
35
|
+
|
36
|
+
tags
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# rubocop:disable Style/EmptyMethod
|
5
|
+
|
6
|
+
module Kirei
|
7
|
+
module Model
|
8
|
+
module BaseClassInterface
|
9
|
+
extend T::Sig
|
10
|
+
extend T::Helpers
|
11
|
+
interface!
|
12
|
+
|
13
|
+
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
14
|
+
def find_by(hash); end
|
15
|
+
|
16
|
+
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
17
|
+
def where(hash); end
|
18
|
+
|
19
|
+
sig { abstract.returns(T.untyped) }
|
20
|
+
def all; end
|
21
|
+
|
22
|
+
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
23
|
+
def create(hash); end
|
24
|
+
|
25
|
+
sig { abstract.params(attributes: T.untyped).void }
|
26
|
+
def wrap_jsonb_non_primivitives!(attributes); end
|
27
|
+
|
28
|
+
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
29
|
+
def resolve(hash); end
|
30
|
+
|
31
|
+
sig { abstract.params(hash: T.untyped).returns(T.untyped) }
|
32
|
+
def resolve_first(hash); end
|
33
|
+
|
34
|
+
sig { abstract.returns(T.untyped) }
|
35
|
+
def table_name; end
|
36
|
+
|
37
|
+
sig { abstract.returns(T.untyped) }
|
38
|
+
def query; end
|
39
|
+
|
40
|
+
sig { abstract.returns(T.untyped) }
|
41
|
+
def db; end
|
42
|
+
|
43
|
+
sig { abstract.returns(T.untyped) }
|
44
|
+
def human_id_length; end
|
45
|
+
|
46
|
+
sig { abstract.returns(T.untyped) }
|
47
|
+
def human_id_prefix; end
|
48
|
+
|
49
|
+
sig { abstract.returns(T.untyped) }
|
50
|
+
def generate_human_id; end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# rubocop:enable Style/EmptyMethod
|