tapioca 0.5.3 → 0.6.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 +114 -23
- data/lib/tapioca/cli.rb +188 -65
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +94 -8
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +5 -4
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +1 -1
- data/lib/tapioca/compilers/dsl/active_record_relations.rb +703 -0
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +43 -13
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +39 -33
- data/lib/tapioca/compilers/dsl/base.rb +26 -42
- data/lib/tapioca/compilers/dsl/extensions/frozen_record.rb +29 -0
- data/lib/tapioca/compilers/dsl/frozen_record.rb +37 -0
- data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +27 -0
- data/lib/tapioca/compilers/dsl/identity_cache.rb +0 -1
- data/lib/tapioca/compilers/dsl/param_helper.rb +52 -0
- data/lib/tapioca/compilers/dsl/rails_generators.rb +120 -0
- data/lib/tapioca/compilers/dsl_compiler.rb +34 -6
- data/lib/tapioca/compilers/sorbet.rb +2 -0
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +51 -48
- data/lib/tapioca/executor.rb +79 -0
- data/lib/tapioca/gemfile.rb +28 -4
- data/lib/tapioca/generators/base.rb +11 -18
- data/lib/tapioca/generators/dsl.rb +33 -38
- data/lib/tapioca/generators/gem.rb +64 -34
- data/lib/tapioca/generators/init.rb +41 -16
- data/lib/tapioca/generators/todo.rb +6 -6
- data/lib/tapioca/helpers/cli_helper.rb +26 -0
- data/lib/tapioca/helpers/config_helper.rb +84 -0
- data/lib/tapioca/helpers/test/content.rb +51 -0
- data/lib/tapioca/helpers/test/isolation.rb +125 -0
- data/lib/tapioca/helpers/test/template.rb +34 -0
- data/lib/tapioca/internal.rb +3 -2
- data/lib/tapioca/rbi_ext/model.rb +13 -10
- data/lib/tapioca/reflection.rb +13 -0
- data/lib/tapioca/trackers/autoload.rb +70 -0
- data/lib/tapioca/trackers/constant_definition.rb +42 -0
- data/lib/tapioca/trackers/mixin.rb +78 -0
- data/lib/tapioca/trackers.rb +14 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +28 -2
- metadata +37 -13
- data/lib/tapioca/config.rb +0 -45
- data/lib/tapioca/config_builder.rb +0 -73
- data/lib/tapioca/constant_locator.rb +0 -34
@@ -0,0 +1,703 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "active_record"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
require "tapioca/compilers/dsl/helper/active_record_constants"
|
11
|
+
|
12
|
+
module Tapioca
|
13
|
+
module Compilers
|
14
|
+
module Dsl
|
15
|
+
# `Tapioca::Compilers::Dsl::ActiveRecordRelations` decorates RBI files for subclasses of
|
16
|
+
# `ActiveRecord::Base` and adds
|
17
|
+
# [relation](http://api.rubyonrails.org/classes/ActiveRecord/Relation.html),
|
18
|
+
# [collection proxy](https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html),
|
19
|
+
# [query](http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html),
|
20
|
+
# [spawn](http://api.rubyonrails.org/classes/ActiveRecord/SpawnMethods.html),
|
21
|
+
# [finder](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html), and
|
22
|
+
# [calculation](http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html) methods.
|
23
|
+
#
|
24
|
+
# The generator defines 3 (synthetic) modules and 3 (synthetic) classes to represent relations properly.
|
25
|
+
#
|
26
|
+
# For a given model `Model`, we generate the following classes:
|
27
|
+
#
|
28
|
+
# 1. A `Model::PrivateRelation` that subclasses `ActiveRecord::Relation`. This synthetic class represents
|
29
|
+
# a relation on `Model` whose methods which return a relation always return a `Model::PrivateRelation` instance.
|
30
|
+
#
|
31
|
+
# 2. `Model::PrivateAssocationRelation` that subclasses `ActiveRecord::AssociationRelation`. This synthetic
|
32
|
+
# class represents a relation on a singular association of type `Model` (e.g. `foo.model`) whose methods which
|
33
|
+
# return a relation will always return a `Model::PrivateAssocationRelation` instance. The difference between this
|
34
|
+
# class and the previous one is mainly that an association relation also keeps track of the resource association
|
35
|
+
# for this relation.
|
36
|
+
#
|
37
|
+
# 3. `Model::PrivateCollectionProxy` that subclasses from `ActiveRecord::Associations::CollectionProxy`.
|
38
|
+
# This synthetic class represents a relation on a plural association of type `Model` (e.g. `foo.models`)
|
39
|
+
# whose methods which return a relation will always return a `Model::PrivateAssocationRelation` instance.
|
40
|
+
# This class represents a collection of `Model` instances with some extra methods to `build`, `create`,
|
41
|
+
# etc new `Model` instances in the collection.
|
42
|
+
#
|
43
|
+
# and the following modules:
|
44
|
+
#
|
45
|
+
# 1. `Model::GeneratedRelationMethods` holds all the relation methods with the return type of
|
46
|
+
# `Model::PrivateRelation`. For example, calling `all` on the `Model` class or an instance of
|
47
|
+
# `Model::PrivateRelation` class will always return a `Model::PrivateRelation` instance, thus the
|
48
|
+
# signature of `all` is defined with that return type in this module.
|
49
|
+
#
|
50
|
+
# 2. `Model::GeneratedAssociationRelationMethods` holds all the relation methods with the return type
|
51
|
+
# of `Model::PrivateAssociationRelation`. For example, calling `all` on an instance of
|
52
|
+
# `Model::PrivateAssociationRelation` or an instance of `Model::PrivateCollectionProxy` class will
|
53
|
+
# always return a `Model::PrivateAssociationRelation` instance, thus the signature of `all` is defined
|
54
|
+
# with that return type in this module.
|
55
|
+
#
|
56
|
+
# 3. `Model::CommonRelationMethods` holds all the relation methods that do not depend on the type of
|
57
|
+
# relation in their return type. For example, `find_by!` will always return the same type (a `Model`
|
58
|
+
# instance), regardless of what kind of relation it is called on, and so belongs in this module.
|
59
|
+
# This module is used to reduce the replication of methods between the previous two modules.
|
60
|
+
#
|
61
|
+
# Additionally, the actual `Model` class extends both `Model::CommonRelationMethods` and
|
62
|
+
# `Model::PrivateRelation` modules, so that, for example, `find_by` and `all` can be chained off of the
|
63
|
+
# `Model` class.
|
64
|
+
#
|
65
|
+
# **CAUTION**: The generated relation classes are named `PrivateXXX` intentionally to reflect the fact
|
66
|
+
# that they represent private subconstants of the Active Record model. As such, these types do not
|
67
|
+
# exist at runtime, and their counterparts that do exist at runtime are marked `private_constant` anyway.
|
68
|
+
# For that reason, these types cannot be used in user code or in `sig`s inside Ruby files, since that will
|
69
|
+
# make the runtime checks fail.
|
70
|
+
#
|
71
|
+
# For example, with the following `ActiveRecord::Base` subclass:
|
72
|
+
#
|
73
|
+
# ~~~rb
|
74
|
+
# class Post < ApplicationRecord
|
75
|
+
# end
|
76
|
+
# ~~~
|
77
|
+
#
|
78
|
+
# this generator will produce the RBI file `post.rbi` with the following content:
|
79
|
+
# ~~~rbi
|
80
|
+
# # post.rbi
|
81
|
+
# # typed: true
|
82
|
+
#
|
83
|
+
# class Post
|
84
|
+
# extend CommonRelationMethods
|
85
|
+
# extend GeneratedRelationMethods
|
86
|
+
#
|
87
|
+
# module CommonRelationMethods
|
88
|
+
# sig { params(block: T.nilable(T.proc.params(record: ::Post).returns(T.untyped))).returns(T::Boolean) }
|
89
|
+
# def any?(&block); end
|
90
|
+
#
|
91
|
+
# # ...
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# module GeneratedAssociationRelationMethods
|
95
|
+
# sig { returns(PrivateAssociationRelation) }
|
96
|
+
# def all; end
|
97
|
+
#
|
98
|
+
# # ...
|
99
|
+
#
|
100
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) }
|
101
|
+
# def where(*args, &blk); end
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# module GeneratedRelationMethods
|
105
|
+
# sig { returns(PrivateRelation) }
|
106
|
+
# def all; end
|
107
|
+
#
|
108
|
+
# # ...
|
109
|
+
#
|
110
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) }
|
111
|
+
# def where(*args, &blk); end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# class PrivateAssociationRelation < ::ActiveRecord::AssociationRelation
|
115
|
+
# include CommonRelationMethods
|
116
|
+
# include GeneratedAssociationRelationMethods
|
117
|
+
#
|
118
|
+
# sig { returns(T::Array[::Post]) }
|
119
|
+
# def to_ary; end
|
120
|
+
#
|
121
|
+
# Elem = type_member(fixed: ::Post)
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# class PrivateCollectionProxy < ::ActiveRecord::Associations::CollectionProxy
|
125
|
+
# include CommonRelationMethods
|
126
|
+
# include GeneratedAssociationRelationMethods
|
127
|
+
#
|
128
|
+
# sig do
|
129
|
+
# params(records: T.any(::Post, T::Array[::Post], T::Array[PrivateCollectionProxy]))
|
130
|
+
# .returns(PrivateCollectionProxy)
|
131
|
+
# end
|
132
|
+
# def <<(*records); end
|
133
|
+
#
|
134
|
+
# # ...
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# class PrivateRelation < ::ActiveRecord::Relation
|
138
|
+
# include CommonRelationMethods
|
139
|
+
# include GeneratedRelationMethods
|
140
|
+
#
|
141
|
+
# sig { returns(T::Array[::Post]) }
|
142
|
+
# def to_ary; end
|
143
|
+
#
|
144
|
+
# Elem = type_member(fixed: ::Post)
|
145
|
+
# end
|
146
|
+
# end
|
147
|
+
# ~~~
|
148
|
+
class ActiveRecordRelations < Base
|
149
|
+
extend T::Sig
|
150
|
+
|
151
|
+
sig do
|
152
|
+
override
|
153
|
+
.params(root: RBI::Tree, constant: T.class_of(::ActiveRecord::Base))
|
154
|
+
.void
|
155
|
+
end
|
156
|
+
def decorate(root, constant)
|
157
|
+
root.create_path(constant) do |model|
|
158
|
+
constant_name = T.must(qualified_name_of(constant))
|
159
|
+
RelationGenerator.new(model, constant_name).generate
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
sig { override.returns(T::Enumerable[Module]) }
|
164
|
+
def gather_constants
|
165
|
+
ActiveRecord::Base.descendants.reject(&:abstract_class?)
|
166
|
+
end
|
167
|
+
|
168
|
+
class RelationGenerator
|
169
|
+
extend T::Sig
|
170
|
+
include ParamHelper
|
171
|
+
include Reflection
|
172
|
+
include Helper::ActiveRecordConstants
|
173
|
+
|
174
|
+
sig do
|
175
|
+
params(
|
176
|
+
model: RBI::Scope,
|
177
|
+
constant_name: String
|
178
|
+
).void
|
179
|
+
end
|
180
|
+
def initialize(model, constant_name)
|
181
|
+
@model = model
|
182
|
+
@constant_name = constant_name
|
183
|
+
@relation_methods_module = T.let(
|
184
|
+
model.create_module(RelationMethodsModuleName),
|
185
|
+
RBI::Scope
|
186
|
+
)
|
187
|
+
@association_relation_methods_module = T.let(
|
188
|
+
model.create_module(AssociationRelationMethodsModuleName),
|
189
|
+
RBI::Scope
|
190
|
+
)
|
191
|
+
@common_relation_methods_module = T.let(
|
192
|
+
model.create_module(CommonRelationMethodsModuleName),
|
193
|
+
RBI::Scope
|
194
|
+
)
|
195
|
+
end
|
196
|
+
|
197
|
+
sig { void }
|
198
|
+
def generate
|
199
|
+
create_classes_and_includes
|
200
|
+
create_common_methods
|
201
|
+
create_relation_methods
|
202
|
+
create_association_relation_methods
|
203
|
+
end
|
204
|
+
|
205
|
+
ASSOCIATION_METHODS = T.let(
|
206
|
+
::ActiveRecord::AssociationRelation.instance_methods -
|
207
|
+
::ActiveRecord::Relation.instance_methods,
|
208
|
+
T::Array[Symbol]
|
209
|
+
)
|
210
|
+
COLLECTION_PROXY_METHODS = T.let(
|
211
|
+
::ActiveRecord::Associations::CollectionProxy.instance_methods -
|
212
|
+
::ActiveRecord::AssociationRelation.instance_methods,
|
213
|
+
T::Array[Symbol]
|
214
|
+
)
|
215
|
+
|
216
|
+
QUERY_METHODS = T.let(begin
|
217
|
+
# Grab all Query methods
|
218
|
+
query_methods = ActiveRecord::QueryMethods.instance_methods(false)
|
219
|
+
# Grab all Spawn methods
|
220
|
+
query_methods |= ActiveRecord::SpawnMethods.instance_methods(false)
|
221
|
+
# Remove the ones we know are private API
|
222
|
+
query_methods -= [:arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
|
223
|
+
# Remove "where" which needs a custom return type for WhereChains
|
224
|
+
query_methods -= [:where]
|
225
|
+
# Remove the methods that ...
|
226
|
+
query_methods
|
227
|
+
.grep_v(/_clause$/) # end with "_clause"
|
228
|
+
.grep_v(/_values?$/) # end with "_value" or "_values"
|
229
|
+
.grep_v(/=$/) # end with "=""
|
230
|
+
.grep_v(/(?<!uniq)!$/) # end with "!" except for "uniq!"
|
231
|
+
end, T::Array[Symbol])
|
232
|
+
WHERE_CHAIN_QUERY_METHODS = T.let(
|
233
|
+
ActiveRecord::QueryMethods::WhereChain.instance_methods(false),
|
234
|
+
T::Array[Symbol]
|
235
|
+
)
|
236
|
+
FINDER_METHODS = T.let(ActiveRecord::FinderMethods.instance_methods(false), T::Array[Symbol])
|
237
|
+
CALCULATION_METHODS = T.let(ActiveRecord::Calculations.instance_methods(false), T::Array[Symbol])
|
238
|
+
ENUMERABLE_QUERY_METHODS = T.let([:any?, :many?, :none?, :one?], T::Array[Symbol])
|
239
|
+
FIND_OR_CREATE_METHODS = T.let(
|
240
|
+
[:find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :create_or_find_by, :create_or_find_by!],
|
241
|
+
T::Array[Symbol]
|
242
|
+
)
|
243
|
+
BUILDER_METHODS = T.let([:new, :build, :create, :create!], T::Array[Symbol])
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
sig { returns(RBI::Scope) }
|
248
|
+
attr_reader :model
|
249
|
+
|
250
|
+
sig { returns(String) }
|
251
|
+
attr_reader :constant_name
|
252
|
+
|
253
|
+
sig { void }
|
254
|
+
def create_classes_and_includes
|
255
|
+
model.create_extend(CommonRelationMethodsModuleName)
|
256
|
+
# The model always extends the generated relation module
|
257
|
+
model.create_extend(RelationMethodsModuleName)
|
258
|
+
|
259
|
+
# This feature is only available in versions of Sorbet with special support for
|
260
|
+
# handling `NilClass` returns from `to_ary`. We should not be typing `to_ary` like
|
261
|
+
# this for older versions since it will make all flatten operations be
|
262
|
+
# `T::Array[NilClass]`, otherwise.
|
263
|
+
if Tapioca::Compilers::Sorbet.supports?(:to_ary_nil_support)
|
264
|
+
# Type the `to_ary` method as returning `NilClass` so that flatten stops recursing
|
265
|
+
# See https://github.com/sorbet/sorbet/pull/4706 for details
|
266
|
+
model.create_method("to_ary", return_type: "NilClass", visibility: RBI::Private.new)
|
267
|
+
end
|
268
|
+
|
269
|
+
create_relation_class
|
270
|
+
create_association_relation_class
|
271
|
+
create_collection_proxy_class
|
272
|
+
end
|
273
|
+
|
274
|
+
sig { void }
|
275
|
+
def create_relation_class
|
276
|
+
superclass = "::ActiveRecord::Relation"
|
277
|
+
|
278
|
+
# The relation subclass includes the generated relation module
|
279
|
+
model.create_class(RelationClassName, superclass_name: superclass) do |klass|
|
280
|
+
klass.create_include(CommonRelationMethodsModuleName)
|
281
|
+
klass.create_include(RelationMethodsModuleName)
|
282
|
+
klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
|
283
|
+
|
284
|
+
klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
|
285
|
+
end
|
286
|
+
|
287
|
+
create_relation_where_chain_class
|
288
|
+
end
|
289
|
+
|
290
|
+
sig { void }
|
291
|
+
def create_association_relation_class
|
292
|
+
superclass = "::ActiveRecord::AssociationRelation"
|
293
|
+
|
294
|
+
# Association subclasses include the generated association relation module
|
295
|
+
model.create_class(AssociationRelationClassName, superclass_name: superclass) do |klass|
|
296
|
+
klass.create_include(CommonRelationMethodsModuleName)
|
297
|
+
klass.create_include(AssociationRelationMethodsModuleName)
|
298
|
+
klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
|
299
|
+
|
300
|
+
klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
|
301
|
+
end
|
302
|
+
|
303
|
+
create_association_relation_where_chain_class
|
304
|
+
end
|
305
|
+
|
306
|
+
sig { void }
|
307
|
+
def create_relation_where_chain_class
|
308
|
+
model.create_class(RelationWhereChainClassName, superclass_name: RelationClassName) do |klass|
|
309
|
+
create_where_chain_methods(klass, RelationClassName)
|
310
|
+
klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
sig { void }
|
315
|
+
def create_association_relation_where_chain_class
|
316
|
+
model.create_class(
|
317
|
+
AssociationRelationWhereChainClassName,
|
318
|
+
superclass_name: AssociationRelationClassName
|
319
|
+
) do |klass|
|
320
|
+
create_where_chain_methods(klass, AssociationRelationClassName)
|
321
|
+
klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
sig { params(klass: RBI::Scope, return_type: String).void }
|
326
|
+
def create_where_chain_methods(klass, return_type)
|
327
|
+
WHERE_CHAIN_QUERY_METHODS.each do |method_name|
|
328
|
+
case method_name
|
329
|
+
when :not
|
330
|
+
klass.create_method(
|
331
|
+
method_name.to_s,
|
332
|
+
parameters: [
|
333
|
+
create_param("opts", type: "T.untyped"),
|
334
|
+
create_rest_param("rest", type: "T.untyped"),
|
335
|
+
],
|
336
|
+
return_type: return_type
|
337
|
+
)
|
338
|
+
when :associated, :missing
|
339
|
+
klass.create_method(
|
340
|
+
method_name.to_s,
|
341
|
+
parameters: [
|
342
|
+
create_rest_param("args", type: "T.untyped"),
|
343
|
+
],
|
344
|
+
return_type: return_type
|
345
|
+
)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
sig { void }
|
351
|
+
def create_collection_proxy_class
|
352
|
+
superclass = "::ActiveRecord::Associations::CollectionProxy"
|
353
|
+
|
354
|
+
# The relation subclass includes the generated association relation module
|
355
|
+
model.create_class(AssociationsCollectionProxyClassName, superclass_name: superclass) do |klass|
|
356
|
+
klass.create_include(CommonRelationMethodsModuleName)
|
357
|
+
klass.create_include(AssociationRelationMethodsModuleName)
|
358
|
+
klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
|
359
|
+
|
360
|
+
klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
|
361
|
+
create_collection_proxy_methods(klass)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
sig { params(klass: RBI::Scope).void }
|
366
|
+
def create_collection_proxy_methods(klass)
|
367
|
+
# For these cases, it is valid to pass:
|
368
|
+
# - a model instance, thus `Model`
|
369
|
+
# - a model collection which can be:
|
370
|
+
# - an array of models, thus `T::Enumerable[Model]`
|
371
|
+
# - an association relation of a model, thus `T::Enumerable[Model]`
|
372
|
+
# - a collection proxy of a model, thus, again, a `T::Enumerable[Model]`
|
373
|
+
# - a collection of relations or collection proxies, thus `T::Enumerable[T::Enumerable[Model]]`
|
374
|
+
# - or, any mix of the above, thus `T::Enumerable[T.any(Model, T::Enumerable[Model])]`
|
375
|
+
# which altogether gives us:
|
376
|
+
# `T.any(Model, T::Enumerable[T.any(Model, T::Enumerable[Model])])`
|
377
|
+
model_collection =
|
378
|
+
"T.any(#{constant_name}, T::Enumerable[T.any(#{constant_name}, T::Enumerable[#{constant_name}])])"
|
379
|
+
|
380
|
+
# For these cases, it is valid to pass the above kind of things, but also:
|
381
|
+
# - a model identifier, which can be:
|
382
|
+
# - a numeric id, thus `Integer`
|
383
|
+
# - a string id, thus `String`
|
384
|
+
# - a collection of identifiers
|
385
|
+
# - a collection of identifiers, thus `T::Enumerable[T.any(Integer, String)]`
|
386
|
+
# which, coupled with the above case, gives us:
|
387
|
+
# `T.any(Model, Integer, String, T::Enumerable[T.any(Model, Integer, String, T::Enumerable[Model])])`
|
388
|
+
model_or_id_collection =
|
389
|
+
"T.any(#{constant_name}, Integer, String" \
|
390
|
+
", T::Enumerable[T.any(#{constant_name}, Integer, String, T::Enumerable[#{constant_name}])])"
|
391
|
+
|
392
|
+
COLLECTION_PROXY_METHODS.each do |method_name|
|
393
|
+
case method_name
|
394
|
+
when :<<, :append, :concat, :prepend, :push
|
395
|
+
klass.create_method(
|
396
|
+
method_name.to_s,
|
397
|
+
parameters: [
|
398
|
+
create_rest_param("records", type: model_collection),
|
399
|
+
],
|
400
|
+
return_type: AssociationsCollectionProxyClassName
|
401
|
+
)
|
402
|
+
when :clear
|
403
|
+
klass.create_method(
|
404
|
+
method_name.to_s,
|
405
|
+
return_type: AssociationsCollectionProxyClassName
|
406
|
+
)
|
407
|
+
when :delete, :destroy
|
408
|
+
klass.create_method(
|
409
|
+
method_name.to_s,
|
410
|
+
parameters: [
|
411
|
+
create_rest_param("records", type: model_or_id_collection),
|
412
|
+
],
|
413
|
+
return_type: "T::Array[#{constant_name}]"
|
414
|
+
)
|
415
|
+
when :load_target
|
416
|
+
klass.create_method(
|
417
|
+
method_name.to_s,
|
418
|
+
return_type: "T::Array[#{constant_name}]"
|
419
|
+
)
|
420
|
+
when :replace
|
421
|
+
klass.create_method(
|
422
|
+
method_name.to_s,
|
423
|
+
parameters: [
|
424
|
+
create_param("other_array", type: model_collection),
|
425
|
+
],
|
426
|
+
return_type: "T::Array[#{constant_name}]"
|
427
|
+
)
|
428
|
+
when :reset_scope
|
429
|
+
# skip
|
430
|
+
when :scope
|
431
|
+
klass.create_method(
|
432
|
+
method_name.to_s,
|
433
|
+
return_type: AssociationRelationClassName
|
434
|
+
)
|
435
|
+
when :target
|
436
|
+
klass.create_method(
|
437
|
+
method_name.to_s,
|
438
|
+
return_type: "T::Array[#{constant_name}]"
|
439
|
+
)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
sig { void }
|
445
|
+
def create_relation_methods
|
446
|
+
create_relation_method("all")
|
447
|
+
create_relation_method(
|
448
|
+
"where",
|
449
|
+
parameters: [
|
450
|
+
create_rest_param("args", type: "T.untyped"),
|
451
|
+
create_block_param("blk", type: "T.untyped"),
|
452
|
+
],
|
453
|
+
relation_return_type: RelationWhereChainClassName,
|
454
|
+
association_return_type: AssociationRelationWhereChainClassName,
|
455
|
+
)
|
456
|
+
|
457
|
+
QUERY_METHODS.each do |method_name|
|
458
|
+
create_relation_method(
|
459
|
+
method_name,
|
460
|
+
parameters: [
|
461
|
+
create_rest_param("args", type: "T.untyped"),
|
462
|
+
create_block_param("blk", type: "T.untyped"),
|
463
|
+
]
|
464
|
+
)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
sig { void }
|
469
|
+
def create_association_relation_methods
|
470
|
+
returning_type = "T.nilable(T.any(T::Array[Symbol], FalseClass))"
|
471
|
+
unique_by_type = "T.nilable(T.any(T::Array[Symbol], Symbol))"
|
472
|
+
|
473
|
+
ASSOCIATION_METHODS.each do |method_name|
|
474
|
+
case method_name
|
475
|
+
when :insert_all, :insert_all!, :upsert_all
|
476
|
+
parameters = [
|
477
|
+
create_param("attributes", type: "T::Array[Hash]"),
|
478
|
+
create_kw_opt_param("returning", type: returning_type, default: "nil"),
|
479
|
+
]
|
480
|
+
|
481
|
+
# Bang methods don't have the `unique_by` parameter
|
482
|
+
unless method_name.end_with?("!")
|
483
|
+
parameters << create_kw_opt_param("unique_by", type: unique_by_type, default: "nil")
|
484
|
+
end
|
485
|
+
|
486
|
+
@association_relation_methods_module.create_method(
|
487
|
+
method_name.to_s,
|
488
|
+
parameters: parameters,
|
489
|
+
return_type: "ActiveRecord::Result"
|
490
|
+
)
|
491
|
+
when :insert, :insert!, :upsert
|
492
|
+
parameters = [
|
493
|
+
create_param("attributes", type: "Hash"),
|
494
|
+
create_kw_opt_param("returning", type: returning_type, default: "nil"),
|
495
|
+
]
|
496
|
+
|
497
|
+
# Bang methods don't have the `unique_by` parameter
|
498
|
+
unless method_name.end_with?("!")
|
499
|
+
parameters << create_kw_opt_param("unique_by", type: unique_by_type, default: "nil")
|
500
|
+
end
|
501
|
+
|
502
|
+
@association_relation_methods_module.create_method(
|
503
|
+
method_name.to_s,
|
504
|
+
parameters: parameters,
|
505
|
+
return_type: "ActiveRecord::Result"
|
506
|
+
)
|
507
|
+
when :proxy_association
|
508
|
+
# skip - private method
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
sig { void }
|
514
|
+
def create_common_methods
|
515
|
+
create_common_method("destroy_all", return_type: "T::Array[#{constant_name}]")
|
516
|
+
|
517
|
+
FINDER_METHODS.each do |method_name|
|
518
|
+
case method_name
|
519
|
+
when :exists?
|
520
|
+
create_common_method(
|
521
|
+
"exists?",
|
522
|
+
parameters: [
|
523
|
+
create_opt_param("conditions", type: "T.untyped", default: ":none"),
|
524
|
+
],
|
525
|
+
return_type: "T::Boolean"
|
526
|
+
)
|
527
|
+
when :include?, :member?
|
528
|
+
create_common_method(
|
529
|
+
method_name,
|
530
|
+
parameters: [
|
531
|
+
create_param("record", type: "T.untyped"),
|
532
|
+
],
|
533
|
+
return_type: "T::Boolean"
|
534
|
+
)
|
535
|
+
when :find, :find_by!
|
536
|
+
create_common_method(
|
537
|
+
"find",
|
538
|
+
parameters: [
|
539
|
+
create_rest_param("args", type: "T.untyped"),
|
540
|
+
],
|
541
|
+
return_type: "T.untyped"
|
542
|
+
)
|
543
|
+
when :find_by
|
544
|
+
create_common_method(
|
545
|
+
"find_by",
|
546
|
+
parameters: [
|
547
|
+
create_rest_param("args", type: "T.untyped"),
|
548
|
+
],
|
549
|
+
return_type: "T.nilable(#{constant_name})"
|
550
|
+
)
|
551
|
+
when :first, :last, :take
|
552
|
+
create_common_method(
|
553
|
+
method_name,
|
554
|
+
parameters: [
|
555
|
+
create_opt_param("limit", type: "T.untyped", default: "nil"),
|
556
|
+
],
|
557
|
+
return_type: "T.untyped"
|
558
|
+
)
|
559
|
+
when :raise_record_not_found_exception!
|
560
|
+
# skip
|
561
|
+
else
|
562
|
+
return_type = if method_name.end_with?("!")
|
563
|
+
constant_name
|
564
|
+
else
|
565
|
+
"T.nilable(#{constant_name})"
|
566
|
+
end
|
567
|
+
|
568
|
+
create_common_method(
|
569
|
+
method_name,
|
570
|
+
return_type: return_type
|
571
|
+
)
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
CALCULATION_METHODS.each do |method_name|
|
576
|
+
case method_name
|
577
|
+
when :average, :maximum, :minimum
|
578
|
+
create_common_method(
|
579
|
+
method_name,
|
580
|
+
parameters: [
|
581
|
+
create_param("column_name", type: "T.any(String, Symbol)"),
|
582
|
+
],
|
583
|
+
return_type: "T.untyped"
|
584
|
+
)
|
585
|
+
when :calculate
|
586
|
+
create_common_method(
|
587
|
+
"calculate",
|
588
|
+
parameters: [
|
589
|
+
create_param("operation", type: "Symbol"),
|
590
|
+
create_param("column_name", type: "T.any(String, Symbol)"),
|
591
|
+
],
|
592
|
+
return_type: "T.untyped"
|
593
|
+
)
|
594
|
+
when :count
|
595
|
+
create_common_method(
|
596
|
+
"count",
|
597
|
+
parameters: [
|
598
|
+
create_opt_param("column_name", type: "T.untyped", default: "nil"),
|
599
|
+
],
|
600
|
+
return_type: "T.untyped"
|
601
|
+
)
|
602
|
+
when :ids
|
603
|
+
create_common_method("ids", return_type: "Array")
|
604
|
+
when :pick, :pluck
|
605
|
+
create_common_method(
|
606
|
+
method_name,
|
607
|
+
parameters: [
|
608
|
+
create_rest_param("column_names", type: "T.untyped"),
|
609
|
+
],
|
610
|
+
return_type: "T.untyped"
|
611
|
+
)
|
612
|
+
when :sum
|
613
|
+
create_common_method(
|
614
|
+
"sum",
|
615
|
+
parameters: [
|
616
|
+
create_opt_param("column_name", type: "T.nilable(T.any(String, Symbol))", default: "nil"),
|
617
|
+
create_block_param("block", type: "T.nilable(T.proc.params(record: T.untyped).returns(T.untyped))"),
|
618
|
+
],
|
619
|
+
return_type: "T.untyped"
|
620
|
+
)
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
ENUMERABLE_QUERY_METHODS.each do |method_name|
|
625
|
+
block_type = "T.nilable(T.proc.params(record: #{constant_name}).returns(T.untyped))"
|
626
|
+
create_common_method(
|
627
|
+
method_name,
|
628
|
+
parameters: [
|
629
|
+
create_block_param("block", type: block_type),
|
630
|
+
],
|
631
|
+
return_type: "T::Boolean"
|
632
|
+
)
|
633
|
+
end
|
634
|
+
|
635
|
+
FIND_OR_CREATE_METHODS.each do |method_name|
|
636
|
+
block_type = "T.nilable(T.proc.params(object: #{constant_name}).void)"
|
637
|
+
create_common_method(
|
638
|
+
method_name,
|
639
|
+
parameters: [
|
640
|
+
create_param("attributes", type: "T.untyped"),
|
641
|
+
create_block_param("block", type: block_type),
|
642
|
+
],
|
643
|
+
return_type: constant_name
|
644
|
+
)
|
645
|
+
end
|
646
|
+
|
647
|
+
BUILDER_METHODS.each do |method_name|
|
648
|
+
create_common_method(
|
649
|
+
method_name,
|
650
|
+
parameters: [
|
651
|
+
create_opt_param("attributes", type: "T.untyped", default: "nil"),
|
652
|
+
create_block_param("block", type: "T.nilable(T.proc.params(object: #{constant_name}).void)"),
|
653
|
+
],
|
654
|
+
return_type: constant_name
|
655
|
+
)
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
sig do
|
660
|
+
params(
|
661
|
+
name: T.any(Symbol, String),
|
662
|
+
parameters: T::Array[RBI::TypedParam],
|
663
|
+
return_type: T.nilable(String)
|
664
|
+
).void
|
665
|
+
end
|
666
|
+
def create_common_method(name, parameters: [], return_type: nil)
|
667
|
+
@common_relation_methods_module.create_method(
|
668
|
+
name.to_s,
|
669
|
+
parameters: parameters,
|
670
|
+
return_type: return_type || "void"
|
671
|
+
)
|
672
|
+
end
|
673
|
+
|
674
|
+
sig do
|
675
|
+
params(
|
676
|
+
name: T.any(Symbol, String),
|
677
|
+
parameters: T::Array[RBI::TypedParam],
|
678
|
+
relation_return_type: String,
|
679
|
+
association_return_type: String,
|
680
|
+
).void
|
681
|
+
end
|
682
|
+
def create_relation_method(
|
683
|
+
name,
|
684
|
+
parameters: [],
|
685
|
+
relation_return_type: RelationClassName,
|
686
|
+
association_return_type: AssociationRelationClassName
|
687
|
+
)
|
688
|
+
@relation_methods_module.create_method(
|
689
|
+
name.to_s,
|
690
|
+
parameters: parameters,
|
691
|
+
return_type: relation_return_type
|
692
|
+
)
|
693
|
+
@association_relation_methods_module.create_method(
|
694
|
+
name.to_s,
|
695
|
+
parameters: parameters,
|
696
|
+
return_type: association_return_type
|
697
|
+
)
|
698
|
+
end
|
699
|
+
end
|
700
|
+
end
|
701
|
+
end
|
702
|
+
end
|
703
|
+
end
|