kirei 0.3.0 → 0.4.0
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 +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/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 +43 -12
- 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 +47 -0
- data/lib/kirei/version.rb +1 -1
- data/lib/kirei.rb +5 -2
- data/sorbet/rbi/shims/ruby.rbi +15 -0
- metadata +29 -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: 788221745d889864fe2ec7075cce289f6d44361dece7711fbe91ac24c16f693b
|
4
|
+
data.tar.gz: e03e3cf0c4c6db69bc0106107ccb002b8f472ba077ef457d88a84b92b188371f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30bef2a1458e1aeeebd774d577df1dc231b0cfa46fb7483b076b69ac05de1529527442599dca18bb278fec42e1a17888eee849feb1b3f81dd502fd92284dd879
|
7
|
+
data.tar.gz: 181df5735f94c222fc29e9bb3c18aa33340995925a324a18a0bb28e1001eaeaee24f8c7442cc5852adf31ae7e5e7a707743441e1c64a585e27039e7b50ba96da
|
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
|