tapioca 0.6.4 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -2
  3. data/README.md +27 -15
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +65 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +16 -9
  7. data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
  8. data/lib/tapioca/{generators → commands}/gem.rb +93 -30
  9. data/lib/tapioca/{generators → commands}/init.rb +9 -13
  10. data/lib/tapioca/{generators → commands}/require.rb +8 -10
  11. data/lib/tapioca/commands/todo.rb +86 -0
  12. data/lib/tapioca/commands.rb +13 -0
  13. data/lib/tapioca/dsl/compiler.rb +185 -0
  14. data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
  15. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
  16. data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
  17. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
  18. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +13 -11
  19. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
  20. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +28 -34
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +18 -16
  22. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
  23. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +12 -8
  24. data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
  25. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
  26. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +11 -16
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +14 -10
  29. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
  30. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
  31. data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +11 -9
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +23 -22
  34. data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
  35. data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +22 -10
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +12 -13
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +11 -9
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +20 -15
  41. data/lib/tapioca/dsl/compilers.rb +31 -0
  42. data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
  43. data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
  44. data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
  45. data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +6 -3
  46. data/lib/tapioca/dsl/pipeline.rb +169 -0
  47. data/lib/tapioca/gem/events.rb +120 -0
  48. data/lib/tapioca/gem/listeners/base.rb +48 -0
  49. data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
  50. data/lib/tapioca/gem/listeners/methods.rb +183 -0
  51. data/lib/tapioca/gem/listeners/mixins.rb +101 -0
  52. data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
  53. data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
  54. data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
  55. data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
  56. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
  57. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
  58. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
  59. data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
  60. data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
  61. data/lib/tapioca/gem/listeners.rb +16 -0
  62. data/lib/tapioca/gem/pipeline.rb +365 -0
  63. data/lib/tapioca/helpers/cli_helper.rb +7 -0
  64. data/lib/tapioca/helpers/config_helper.rb +5 -8
  65. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  66. data/lib/tapioca/helpers/signatures_helper.rb +17 -0
  67. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  68. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  69. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  70. data/lib/tapioca/helpers/test/template.rb +13 -2
  71. data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
  72. data/lib/tapioca/internal.rb +18 -10
  73. data/lib/tapioca/rbi_ext/model.rb +14 -50
  74. data/lib/tapioca/rbi_formatter.rb +37 -0
  75. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
  76. data/lib/tapioca/runtime/generic_type_registry.rb +168 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +157 -0
  79. data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
  80. data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
  81. data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
  82. data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
  83. data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
  84. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +69 -34
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +2 -2
  87. data/lib/tapioca/static/symbol_loader.rb +83 -0
  88. data/lib/tapioca/static/symbol_table_parser.rb +63 -0
  89. data/lib/tapioca/version.rb +1 -1
  90. data/lib/tapioca.rb +2 -7
  91. metadata +83 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -720
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -195
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dsl_compiler.rb +0 -134
  96. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -223
  97. data/lib/tapioca/compilers/sorbet.rb +0 -59
  98. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  99. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  100. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  101. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  102. data/lib/tapioca/generators/todo.rb +0 -76
  103. data/lib/tapioca/generators.rb +0 -9
  104. data/lib/tapioca/generic_type_registry.rb +0 -164
  105. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -108
  106. data/lib/tapioca/loader.rb +0 -119
  107. data/lib/tapioca/reflection.rb +0 -151
  108. data/lib/tapioca/trackers/autoload.rb +0 -70
  109. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  110. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -1,720 +0,0 @@
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 { params(type: String).returns(String) }
254
- def as_nilable_type(type)
255
- if type.start_with?("T.nilable(", "::T.nilable(") || type == "T.untyped" || type == "::T.untyped"
256
- type
257
- else
258
- "T.nilable(#{type})"
259
- end
260
- end
261
-
262
- sig { void }
263
- def create_classes_and_includes
264
- model.create_extend(CommonRelationMethodsModuleName)
265
- # The model always extends the generated relation module
266
- model.create_extend(RelationMethodsModuleName)
267
-
268
- # This feature is only available in versions of Sorbet with special support for
269
- # handling `NilClass` returns from `to_ary`. We should not be typing `to_ary` like
270
- # this for older versions since it will make all flatten operations be
271
- # `T::Array[NilClass]`, otherwise.
272
- if Tapioca::Compilers::Sorbet.supports?(:to_ary_nil_support)
273
- # Type the `to_ary` method as returning `NilClass` so that flatten stops recursing
274
- # See https://github.com/sorbet/sorbet/pull/4706 for details
275
- model.create_method("to_ary", return_type: "NilClass", visibility: RBI::Private.new)
276
- end
277
-
278
- create_relation_class
279
- create_association_relation_class
280
- create_collection_proxy_class
281
- end
282
-
283
- sig { void }
284
- def create_relation_class
285
- superclass = "::ActiveRecord::Relation"
286
-
287
- # The relation subclass includes the generated relation module
288
- model.create_class(RelationClassName, superclass_name: superclass) do |klass|
289
- klass.create_include(CommonRelationMethodsModuleName)
290
- klass.create_include(RelationMethodsModuleName)
291
- klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
292
-
293
- klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
294
- end
295
-
296
- create_relation_where_chain_class
297
- end
298
-
299
- sig { void }
300
- def create_association_relation_class
301
- superclass = "::ActiveRecord::AssociationRelation"
302
-
303
- # Association subclasses include the generated association relation module
304
- model.create_class(AssociationRelationClassName, superclass_name: superclass) do |klass|
305
- klass.create_include(CommonRelationMethodsModuleName)
306
- klass.create_include(AssociationRelationMethodsModuleName)
307
- klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
308
-
309
- klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
310
- end
311
-
312
- create_association_relation_where_chain_class
313
- end
314
-
315
- sig { void }
316
- def create_relation_where_chain_class
317
- model.create_class(RelationWhereChainClassName, superclass_name: RelationClassName) do |klass|
318
- create_where_chain_methods(klass, RelationClassName)
319
- klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
320
- end
321
- end
322
-
323
- sig { void }
324
- def create_association_relation_where_chain_class
325
- model.create_class(
326
- AssociationRelationWhereChainClassName,
327
- superclass_name: AssociationRelationClassName
328
- ) do |klass|
329
- create_where_chain_methods(klass, AssociationRelationClassName)
330
- klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
331
- end
332
- end
333
-
334
- sig { params(klass: RBI::Scope, return_type: String).void }
335
- def create_where_chain_methods(klass, return_type)
336
- WHERE_CHAIN_QUERY_METHODS.each do |method_name|
337
- case method_name
338
- when :not
339
- klass.create_method(
340
- method_name.to_s,
341
- parameters: [
342
- create_param("opts", type: "T.untyped"),
343
- create_rest_param("rest", type: "T.untyped"),
344
- ],
345
- return_type: return_type
346
- )
347
- when :associated, :missing
348
- klass.create_method(
349
- method_name.to_s,
350
- parameters: [
351
- create_rest_param("args", type: "T.untyped"),
352
- ],
353
- return_type: return_type
354
- )
355
- end
356
- end
357
- end
358
-
359
- sig { void }
360
- def create_collection_proxy_class
361
- superclass = "::ActiveRecord::Associations::CollectionProxy"
362
-
363
- # The relation subclass includes the generated association relation module
364
- model.create_class(AssociationsCollectionProxyClassName, superclass_name: superclass) do |klass|
365
- klass.create_include(CommonRelationMethodsModuleName)
366
- klass.create_include(AssociationRelationMethodsModuleName)
367
- klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
368
-
369
- klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
370
- create_collection_proxy_methods(klass)
371
- end
372
- end
373
-
374
- sig { params(klass: RBI::Scope).void }
375
- def create_collection_proxy_methods(klass)
376
- # For these cases, it is valid to pass:
377
- # - a model instance, thus `Model`
378
- # - a model collection which can be:
379
- # - an array of models, thus `T::Enumerable[Model]`
380
- # - an association relation of a model, thus `T::Enumerable[Model]`
381
- # - a collection proxy of a model, thus, again, a `T::Enumerable[Model]`
382
- # - a collection of relations or collection proxies, thus `T::Enumerable[T::Enumerable[Model]]`
383
- # - or, any mix of the above, thus `T::Enumerable[T.any(Model, T::Enumerable[Model])]`
384
- # which altogether gives us:
385
- # `T.any(Model, T::Enumerable[T.any(Model, T::Enumerable[Model])])`
386
- model_collection =
387
- "T.any(#{constant_name}, T::Enumerable[T.any(#{constant_name}, T::Enumerable[#{constant_name}])])"
388
-
389
- # For these cases, it is valid to pass the above kind of things, but also:
390
- # - a model identifier, which can be:
391
- # - a numeric id, thus `Integer`
392
- # - a string id, thus `String`
393
- # - a collection of identifiers
394
- # - a collection of identifiers, thus `T::Enumerable[T.any(Integer, String)]`
395
- # which, coupled with the above case, gives us:
396
- # `T.any(Model, Integer, String, T::Enumerable[T.any(Model, Integer, String, T::Enumerable[Model])])`
397
- model_or_id_collection =
398
- "T.any(#{constant_name}, Integer, String" \
399
- ", T::Enumerable[T.any(#{constant_name}, Integer, String, T::Enumerable[#{constant_name}])])"
400
-
401
- COLLECTION_PROXY_METHODS.each do |method_name|
402
- case method_name
403
- when :<<, :append, :concat, :prepend, :push
404
- klass.create_method(
405
- method_name.to_s,
406
- parameters: [
407
- create_rest_param("records", type: model_collection),
408
- ],
409
- return_type: AssociationsCollectionProxyClassName
410
- )
411
- when :clear
412
- klass.create_method(
413
- method_name.to_s,
414
- return_type: AssociationsCollectionProxyClassName
415
- )
416
- when :delete, :destroy
417
- klass.create_method(
418
- method_name.to_s,
419
- parameters: [
420
- create_rest_param("records", type: model_or_id_collection),
421
- ],
422
- return_type: "T::Array[#{constant_name}]"
423
- )
424
- when :load_target
425
- klass.create_method(
426
- method_name.to_s,
427
- return_type: "T::Array[#{constant_name}]"
428
- )
429
- when :replace
430
- klass.create_method(
431
- method_name.to_s,
432
- parameters: [
433
- create_param("other_array", type: model_collection),
434
- ],
435
- return_type: "T::Array[#{constant_name}]"
436
- )
437
- when :reset_scope
438
- # skip
439
- when :scope
440
- klass.create_method(
441
- method_name.to_s,
442
- return_type: AssociationRelationClassName
443
- )
444
- when :target
445
- klass.create_method(
446
- method_name.to_s,
447
- return_type: "T::Array[#{constant_name}]"
448
- )
449
- end
450
- end
451
- end
452
-
453
- sig { void }
454
- def create_relation_methods
455
- create_relation_method("all")
456
- create_relation_method(
457
- "where",
458
- parameters: [
459
- create_rest_param("args", type: "T.untyped"),
460
- create_block_param("blk", type: "T.untyped"),
461
- ],
462
- relation_return_type: RelationWhereChainClassName,
463
- association_return_type: AssociationRelationWhereChainClassName,
464
- )
465
-
466
- QUERY_METHODS.each do |method_name|
467
- create_relation_method(
468
- method_name,
469
- parameters: [
470
- create_rest_param("args", type: "T.untyped"),
471
- create_block_param("blk", type: "T.untyped"),
472
- ]
473
- )
474
- end
475
- end
476
-
477
- sig { void }
478
- def create_association_relation_methods
479
- returning_type = "T.nilable(T.any(T::Array[Symbol], FalseClass))"
480
- unique_by_type = "T.nilable(T.any(T::Array[Symbol], Symbol))"
481
-
482
- ASSOCIATION_METHODS.each do |method_name|
483
- case method_name
484
- when :insert_all, :insert_all!, :upsert_all
485
- parameters = [
486
- create_param("attributes", type: "T::Array[Hash]"),
487
- create_kw_opt_param("returning", type: returning_type, default: "nil"),
488
- ]
489
-
490
- # Bang methods don't have the `unique_by` parameter
491
- unless method_name.end_with?("!")
492
- parameters << create_kw_opt_param("unique_by", type: unique_by_type, default: "nil")
493
- end
494
-
495
- @association_relation_methods_module.create_method(
496
- method_name.to_s,
497
- parameters: parameters,
498
- return_type: "ActiveRecord::Result"
499
- )
500
- when :insert, :insert!, :upsert
501
- parameters = [
502
- create_param("attributes", type: "Hash"),
503
- create_kw_opt_param("returning", type: returning_type, default: "nil"),
504
- ]
505
-
506
- # Bang methods don't have the `unique_by` parameter
507
- unless method_name.end_with?("!")
508
- parameters << create_kw_opt_param("unique_by", type: unique_by_type, default: "nil")
509
- end
510
-
511
- @association_relation_methods_module.create_method(
512
- method_name.to_s,
513
- parameters: parameters,
514
- return_type: "ActiveRecord::Result"
515
- )
516
- when :proxy_association
517
- # skip - private method
518
- end
519
- end
520
- end
521
-
522
- sig { void }
523
- def create_common_methods
524
- create_common_method("destroy_all", return_type: "T::Array[#{constant_name}]")
525
-
526
- FINDER_METHODS.each do |method_name|
527
- case method_name
528
- when :exists?
529
- create_common_method(
530
- "exists?",
531
- parameters: [
532
- create_opt_param("conditions", type: "T.untyped", default: ":none"),
533
- ],
534
- return_type: "T::Boolean"
535
- )
536
- when :include?, :member?
537
- create_common_method(
538
- method_name,
539
- parameters: [
540
- create_param("record", type: "T.untyped"),
541
- ],
542
- return_type: "T::Boolean"
543
- )
544
- when :find
545
- create_common_method(
546
- "find",
547
- parameters: [
548
- create_rest_param("args", type: "T.untyped"),
549
- ],
550
- return_type: "T.untyped"
551
- )
552
- when :find_by
553
- create_common_method(
554
- "find_by",
555
- parameters: [
556
- create_rest_param("args", type: "T.untyped"),
557
- ],
558
- return_type: as_nilable_type(constant_name)
559
- )
560
- when :find_by!
561
- create_common_method(
562
- "find_by!",
563
- parameters: [
564
- create_rest_param("args", type: "T.untyped"),
565
- ],
566
- return_type: constant_name
567
- )
568
- when :first, :last, :take
569
- create_common_method(
570
- method_name,
571
- parameters: [
572
- create_opt_param("limit", type: "T.untyped", default: "nil"),
573
- ],
574
- return_type: "T.untyped"
575
- )
576
- when :raise_record_not_found_exception!
577
- # skip
578
- else
579
- return_type = if method_name.end_with?("!")
580
- constant_name
581
- else
582
- as_nilable_type(constant_name)
583
- end
584
-
585
- create_common_method(
586
- method_name,
587
- return_type: return_type
588
- )
589
- end
590
- end
591
-
592
- CALCULATION_METHODS.each do |method_name|
593
- case method_name
594
- when :average, :maximum, :minimum
595
- create_common_method(
596
- method_name,
597
- parameters: [
598
- create_param("column_name", type: "T.any(String, Symbol)"),
599
- ],
600
- return_type: "T.untyped"
601
- )
602
- when :calculate
603
- create_common_method(
604
- "calculate",
605
- parameters: [
606
- create_param("operation", type: "Symbol"),
607
- create_param("column_name", type: "T.any(String, Symbol)"),
608
- ],
609
- return_type: "T.untyped"
610
- )
611
- when :count
612
- create_common_method(
613
- "count",
614
- parameters: [
615
- create_opt_param("column_name", type: "T.untyped", default: "nil"),
616
- ],
617
- return_type: "T.untyped"
618
- )
619
- when :ids
620
- create_common_method("ids", return_type: "Array")
621
- when :pick, :pluck
622
- create_common_method(
623
- method_name,
624
- parameters: [
625
- create_rest_param("column_names", type: "T.untyped"),
626
- ],
627
- return_type: "T.untyped"
628
- )
629
- when :sum
630
- create_common_method(
631
- "sum",
632
- parameters: [
633
- create_opt_param("column_name", type: "T.nilable(T.any(String, Symbol))", default: "nil"),
634
- create_block_param("block", type: "T.nilable(T.proc.params(record: T.untyped).returns(T.untyped))"),
635
- ],
636
- return_type: "T.untyped"
637
- )
638
- end
639
- end
640
-
641
- ENUMERABLE_QUERY_METHODS.each do |method_name|
642
- block_type = "T.nilable(T.proc.params(record: #{constant_name}).returns(T.untyped))"
643
- create_common_method(
644
- method_name,
645
- parameters: [
646
- create_block_param("block", type: block_type),
647
- ],
648
- return_type: "T::Boolean"
649
- )
650
- end
651
-
652
- FIND_OR_CREATE_METHODS.each do |method_name|
653
- block_type = "T.nilable(T.proc.params(object: #{constant_name}).void)"
654
- create_common_method(
655
- method_name,
656
- parameters: [
657
- create_param("attributes", type: "T.untyped"),
658
- create_block_param("block", type: block_type),
659
- ],
660
- return_type: constant_name
661
- )
662
- end
663
-
664
- BUILDER_METHODS.each do |method_name|
665
- create_common_method(
666
- method_name,
667
- parameters: [
668
- create_opt_param("attributes", type: "T.untyped", default: "nil"),
669
- create_block_param("block", type: "T.nilable(T.proc.params(object: #{constant_name}).void)"),
670
- ],
671
- return_type: constant_name
672
- )
673
- end
674
- end
675
-
676
- sig do
677
- params(
678
- name: T.any(Symbol, String),
679
- parameters: T::Array[RBI::TypedParam],
680
- return_type: T.nilable(String)
681
- ).void
682
- end
683
- def create_common_method(name, parameters: [], return_type: nil)
684
- @common_relation_methods_module.create_method(
685
- name.to_s,
686
- parameters: parameters,
687
- return_type: return_type || "void"
688
- )
689
- end
690
-
691
- sig do
692
- params(
693
- name: T.any(Symbol, String),
694
- parameters: T::Array[RBI::TypedParam],
695
- relation_return_type: String,
696
- association_return_type: String,
697
- ).void
698
- end
699
- def create_relation_method(
700
- name,
701
- parameters: [],
702
- relation_return_type: RelationClassName,
703
- association_return_type: AssociationRelationClassName
704
- )
705
- @relation_methods_module.create_method(
706
- name.to_s,
707
- parameters: parameters,
708
- return_type: relation_return_type
709
- )
710
- @association_relation_methods_module.create_method(
711
- name.to_s,
712
- parameters: parameters,
713
- return_type: association_return_type
714
- )
715
- end
716
- end
717
- end
718
- end
719
- end
720
- end