tapioca 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +13 -2
  3. data/README.md +79 -25
  4. data/Rakefile +10 -14
  5. data/lib/tapioca/cli.rb +66 -80
  6. data/lib/tapioca/{generators/base.rb → commands/command.rb} +17 -10
  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 +84 -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 +32 -24
  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 +29 -35
  21. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +26 -24
  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 +10 -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 +12 -17
  27. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
  28. data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +11 -11
  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 +10 -8
  32. data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
  33. data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +28 -25
  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 +10 -8
  36. data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +13 -14
  37. data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
  38. data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +12 -13
  39. data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
  40. data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +16 -14
  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 +2 -2
  46. data/lib/tapioca/{compilers/dsl_compiler.rb → dsl/pipeline.rb} +41 -33
  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/gemfile.rb +44 -20
  64. data/lib/tapioca/helpers/cli_helper.rb +16 -8
  65. data/lib/tapioca/helpers/config_helper.rb +113 -0
  66. data/lib/tapioca/helpers/rbi_helper.rb +17 -0
  67. data/lib/tapioca/helpers/shims_helper.rb +87 -0
  68. data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
  69. data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
  70. data/lib/tapioca/helpers/test/isolation.rb +1 -1
  71. data/lib/tapioca/helpers/test/template.rb +13 -2
  72. data/lib/tapioca/internal.rb +17 -10
  73. data/lib/tapioca/rbi_ext/model.rb +2 -48
  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 +166 -0
  77. data/lib/tapioca/runtime/loader.rb +123 -0
  78. data/lib/tapioca/runtime/reflection.rb +153 -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 +110 -54
  85. data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
  86. data/lib/tapioca/{compilers → static}/requires_compiler.rb +5 -12
  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 +82 -62
  92. data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -711
  93. data/lib/tapioca/compilers/dsl/base.rb +0 -179
  94. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
  95. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -198
  96. data/lib/tapioca/compilers/sorbet.rb +0 -59
  97. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
  98. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
  99. data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
  100. data/lib/tapioca/compilers/todos_compiler.rb +0 -32
  101. data/lib/tapioca/generators/todo.rb +0 -76
  102. data/lib/tapioca/generators.rb +0 -9
  103. data/lib/tapioca/generic_type_registry.rb +0 -149
  104. data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -98
  105. data/lib/tapioca/loader.rb +0 -119
  106. data/lib/tapioca/reflection.rb +0 -151
  107. data/lib/tapioca/trackers/autoload.rb +0 -70
  108. data/lib/tapioca/trackers/constant_definition.rb +0 -42
  109. data/lib/tapioca/trackers/mixin.rb +0 -78
@@ -0,0 +1,712 @@
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/dsl/helpers/active_record_constants_helper"
11
+
12
+ module Tapioca
13
+ module Dsl
14
+ module Compilers
15
+ # `Tapioca::Dsl::Compilers::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 compiler 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 compiler 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 < Compiler
149
+ extend T::Sig
150
+ include Helpers::ActiveRecordConstantsHelper
151
+ include SorbetHelper
152
+
153
+ ConstantType = type_member(fixed: T.class_of(::ActiveRecord::Base))
154
+
155
+ sig { override.void }
156
+ def decorate
157
+ root.create_path(constant) do |model|
158
+ relation_methods_module = model.create_module(RelationMethodsModuleName)
159
+ association_relation_methods_module = model.create_module(AssociationRelationMethodsModuleName)
160
+ common_relation_methods_module = model.create_module(CommonRelationMethodsModuleName)
161
+
162
+ create_classes_and_includes(model)
163
+ create_common_methods(common_relation_methods_module)
164
+ create_relation_methods(relation_methods_module, association_relation_methods_module)
165
+ create_association_relation_methods(association_relation_methods_module)
166
+ end
167
+ end
168
+
169
+ sig { override.returns(T::Enumerable[Module]) }
170
+ def self.gather_constants
171
+ ActiveRecord::Base.descendants.reject(&:abstract_class?)
172
+ end
173
+
174
+ ASSOCIATION_METHODS = T.let(
175
+ ::ActiveRecord::AssociationRelation.instance_methods -
176
+ ::ActiveRecord::Relation.instance_methods,
177
+ T::Array[Symbol]
178
+ )
179
+ COLLECTION_PROXY_METHODS = T.let(
180
+ ::ActiveRecord::Associations::CollectionProxy.instance_methods -
181
+ ::ActiveRecord::AssociationRelation.instance_methods,
182
+ T::Array[Symbol]
183
+ )
184
+
185
+ QUERY_METHODS = T.let(begin
186
+ # Grab all Query methods
187
+ query_methods = ActiveRecord::QueryMethods.instance_methods(false)
188
+ # Grab all Spawn methods
189
+ query_methods |= ActiveRecord::SpawnMethods.instance_methods(false)
190
+ # Remove the ones we know are private API
191
+ query_methods -= [:arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
192
+ # Remove "where" which needs a custom return type for WhereChains
193
+ query_methods -= [:where]
194
+ # Remove the methods that ...
195
+ query_methods
196
+ .grep_v(/_clause$/) # end with "_clause"
197
+ .grep_v(/_values?$/) # end with "_value" or "_values"
198
+ .grep_v(/=$/) # end with "=""
199
+ .grep_v(/(?<!uniq)!$/) # end with "!" except for "uniq!"
200
+ end, T::Array[Symbol])
201
+ WHERE_CHAIN_QUERY_METHODS = T.let(
202
+ ActiveRecord::QueryMethods::WhereChain.instance_methods(false),
203
+ T::Array[Symbol]
204
+ )
205
+ FINDER_METHODS = T.let(ActiveRecord::FinderMethods.instance_methods(false), T::Array[Symbol])
206
+ CALCULATION_METHODS = T.let(ActiveRecord::Calculations.instance_methods(false), T::Array[Symbol])
207
+ ENUMERABLE_QUERY_METHODS = T.let([:any?, :many?, :none?, :one?], T::Array[Symbol])
208
+ FIND_OR_CREATE_METHODS = T.let(
209
+ [:find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :create_or_find_by, :create_or_find_by!],
210
+ T::Array[Symbol]
211
+ )
212
+ BUILDER_METHODS = T.let([:new, :build, :create, :create!], T::Array[Symbol])
213
+
214
+ private
215
+
216
+ sig { returns(String) }
217
+ def constant_name
218
+ @constant_name ||= T.let(T.must(qualified_name_of(constant)), T.nilable(String))
219
+ T.must(@constant_name)
220
+ end
221
+
222
+ sig { params(method_name: Symbol).returns(T::Boolean) }
223
+ def bang_method?(method_name)
224
+ method_name.to_s.end_with?("!")
225
+ end
226
+
227
+ sig { params(model: RBI::Scope).void }
228
+ def create_classes_and_includes(model)
229
+ model.create_extend(CommonRelationMethodsModuleName)
230
+ # The model always extends the generated relation module
231
+ model.create_extend(RelationMethodsModuleName)
232
+
233
+ # This feature is only available in versions of Sorbet with special support for
234
+ # handling `NilClass` returns from `to_ary`. We should not be typing `to_ary` like
235
+ # this for older versions since it will make all flatten operations be
236
+ # `T::Array[NilClass]`, otherwise.
237
+ if sorbet_supports?(:to_ary_nil_support)
238
+ # Type the `to_ary` method as returning `NilClass` so that flatten stops recursing
239
+ # See https://github.com/sorbet/sorbet/pull/4706 for details
240
+ model.create_method("to_ary", return_type: "NilClass", visibility: RBI::Private.new)
241
+ end
242
+
243
+ create_relation_class(model)
244
+ create_association_relation_class(model)
245
+ create_collection_proxy_class(model)
246
+ end
247
+
248
+ sig { params(model: RBI::Scope).void }
249
+ def create_relation_class(model)
250
+ superclass = "::ActiveRecord::Relation"
251
+
252
+ # The relation subclass includes the generated relation module
253
+ model.create_class(RelationClassName, superclass_name: superclass) do |klass|
254
+ klass.create_include(CommonRelationMethodsModuleName)
255
+ klass.create_include(RelationMethodsModuleName)
256
+ klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
257
+
258
+ klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
259
+ end
260
+
261
+ create_relation_where_chain_class(model)
262
+ end
263
+
264
+ sig { params(model: RBI::Scope).void }
265
+ def create_association_relation_class(model)
266
+ superclass = "::ActiveRecord::AssociationRelation"
267
+
268
+ # Association subclasses include the generated association relation module
269
+ model.create_class(AssociationRelationClassName, superclass_name: superclass) do |klass|
270
+ klass.create_include(CommonRelationMethodsModuleName)
271
+ klass.create_include(AssociationRelationMethodsModuleName)
272
+ klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
273
+
274
+ klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
275
+ end
276
+
277
+ create_association_relation_where_chain_class(model)
278
+ end
279
+
280
+ sig { params(model: RBI::Scope).void }
281
+ def create_relation_where_chain_class(model)
282
+ model.create_class(RelationWhereChainClassName, superclass_name: RelationClassName) do |klass|
283
+ create_where_chain_methods(klass, RelationClassName)
284
+ klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
285
+ end
286
+ end
287
+
288
+ sig { params(model: RBI::Scope).void }
289
+ def create_association_relation_where_chain_class(model)
290
+ model.create_class(
291
+ AssociationRelationWhereChainClassName,
292
+ superclass_name: AssociationRelationClassName
293
+ ) do |klass|
294
+ create_where_chain_methods(klass, AssociationRelationClassName)
295
+ klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
296
+ end
297
+ end
298
+
299
+ sig { params(klass: RBI::Scope, return_type: String).void }
300
+ def create_where_chain_methods(klass, return_type)
301
+ WHERE_CHAIN_QUERY_METHODS.each do |method_name|
302
+ case method_name
303
+ when :not
304
+ klass.create_method(
305
+ method_name.to_s,
306
+ parameters: [
307
+ create_param("opts", type: "T.untyped"),
308
+ create_rest_param("rest", type: "T.untyped"),
309
+ ],
310
+ return_type: return_type
311
+ )
312
+ when :associated, :missing
313
+ klass.create_method(
314
+ method_name.to_s,
315
+ parameters: [
316
+ create_rest_param("args", type: "T.untyped"),
317
+ ],
318
+ return_type: return_type
319
+ )
320
+ end
321
+ end
322
+ end
323
+
324
+ sig { params(model: RBI::Scope).void }
325
+ def create_collection_proxy_class(model)
326
+ superclass = "::ActiveRecord::Associations::CollectionProxy"
327
+
328
+ # The relation subclass includes the generated association relation module
329
+ model.create_class(AssociationsCollectionProxyClassName, superclass_name: superclass) do |klass|
330
+ klass.create_include(CommonRelationMethodsModuleName)
331
+ klass.create_include(AssociationRelationMethodsModuleName)
332
+ klass.create_constant("Elem", value: "type_member(fixed: #{constant_name})")
333
+
334
+ klass.create_method("to_ary", return_type: "T::Array[#{constant_name}]")
335
+ create_collection_proxy_methods(klass)
336
+ end
337
+ end
338
+
339
+ sig { params(klass: RBI::Scope).void }
340
+ def create_collection_proxy_methods(klass)
341
+ # For these cases, it is valid to pass:
342
+ # - a model instance, thus `Model`
343
+ # - a model collection which can be:
344
+ # - an array of models, thus `T::Enumerable[Model]`
345
+ # - an association relation of a model, thus `T::Enumerable[Model]`
346
+ # - a collection proxy of a model, thus, again, a `T::Enumerable[Model]`
347
+ # - a collection of relations or collection proxies, thus `T::Enumerable[T::Enumerable[Model]]`
348
+ # - or, any mix of the above, thus `T::Enumerable[T.any(Model, T::Enumerable[Model])]`
349
+ # which altogether gives us:
350
+ # `T.any(Model, T::Enumerable[T.any(Model, T::Enumerable[Model])])`
351
+ model_collection =
352
+ "T.any(#{constant_name}, T::Enumerable[T.any(#{constant_name}, T::Enumerable[#{constant_name}])])"
353
+
354
+ # For these cases, it is valid to pass the above kind of things, but also:
355
+ # - a model identifier, which can be:
356
+ # - a numeric id, thus `Integer`
357
+ # - a string id, thus `String`
358
+ # - a collection of identifiers
359
+ # - a collection of identifiers, thus `T::Enumerable[T.any(Integer, String)]`
360
+ # which, coupled with the above case, gives us:
361
+ # `T.any(Model, Integer, String, T::Enumerable[T.any(Model, Integer, String, T::Enumerable[Model])])`
362
+ model_or_id_collection =
363
+ "T.any(#{constant_name}, Integer, String" \
364
+ ", T::Enumerable[T.any(#{constant_name}, Integer, String, T::Enumerable[#{constant_name}])])"
365
+
366
+ COLLECTION_PROXY_METHODS.each do |method_name|
367
+ case method_name
368
+ when :<<, :append, :concat, :prepend, :push
369
+ klass.create_method(
370
+ method_name.to_s,
371
+ parameters: [
372
+ create_rest_param("records", type: model_collection),
373
+ ],
374
+ return_type: AssociationsCollectionProxyClassName
375
+ )
376
+ when :clear
377
+ klass.create_method(
378
+ method_name.to_s,
379
+ return_type: AssociationsCollectionProxyClassName
380
+ )
381
+ when :delete, :destroy
382
+ klass.create_method(
383
+ method_name.to_s,
384
+ parameters: [
385
+ create_rest_param("records", type: model_or_id_collection),
386
+ ],
387
+ return_type: "T::Array[#{constant_name}]"
388
+ )
389
+ when :load_target
390
+ klass.create_method(
391
+ method_name.to_s,
392
+ return_type: "T::Array[#{constant_name}]"
393
+ )
394
+ when :replace
395
+ klass.create_method(
396
+ method_name.to_s,
397
+ parameters: [
398
+ create_param("other_array", type: model_collection),
399
+ ],
400
+ return_type: "T::Array[#{constant_name}]"
401
+ )
402
+ when :reset_scope
403
+ # skip
404
+ when :scope
405
+ klass.create_method(
406
+ method_name.to_s,
407
+ return_type: AssociationRelationClassName
408
+ )
409
+ when :target
410
+ klass.create_method(
411
+ method_name.to_s,
412
+ return_type: "T::Array[#{constant_name}]"
413
+ )
414
+ end
415
+ end
416
+ end
417
+
418
+ sig { params(relation_methods_module: RBI::Scope, association_relation_methods_module: RBI::Scope).void }
419
+ def create_relation_methods(relation_methods_module, association_relation_methods_module)
420
+ create_relation_method("all", relation_methods_module, association_relation_methods_module)
421
+ create_relation_method(
422
+ "where",
423
+ relation_methods_module,
424
+ association_relation_methods_module,
425
+ parameters: [
426
+ create_rest_param("args", type: "T.untyped"),
427
+ create_block_param("blk", type: "T.untyped"),
428
+ ],
429
+ relation_return_type: RelationWhereChainClassName,
430
+ association_return_type: AssociationRelationWhereChainClassName,
431
+ )
432
+
433
+ QUERY_METHODS.each do |method_name|
434
+ create_relation_method(
435
+ method_name,
436
+ relation_methods_module,
437
+ association_relation_methods_module,
438
+ parameters: [
439
+ create_rest_param("args", type: "T.untyped"),
440
+ create_block_param("blk", type: "T.untyped"),
441
+ ]
442
+ )
443
+ end
444
+ end
445
+
446
+ sig { params(association_relation_methods_module: RBI::Scope).void }
447
+ def create_association_relation_methods(association_relation_methods_module)
448
+ returning_type = "T.nilable(T.any(T::Array[Symbol], FalseClass))"
449
+ unique_by_type = "T.nilable(T.any(T::Array[Symbol], Symbol))"
450
+
451
+ ASSOCIATION_METHODS.each do |method_name|
452
+ case method_name
453
+ when :insert_all, :insert_all!, :upsert_all
454
+ parameters = [
455
+ create_param("attributes", type: "T::Array[Hash]"),
456
+ create_kw_opt_param("returning", type: returning_type, default: "nil"),
457
+ ]
458
+
459
+ # Bang methods don't have the `unique_by` parameter
460
+ unless bang_method?(method_name)
461
+ parameters << create_kw_opt_param("unique_by", type: unique_by_type, default: "nil")
462
+ end
463
+
464
+ association_relation_methods_module.create_method(
465
+ method_name.to_s,
466
+ parameters: parameters,
467
+ return_type: "ActiveRecord::Result"
468
+ )
469
+ when :insert, :insert!, :upsert
470
+ parameters = [
471
+ create_param("attributes", type: "Hash"),
472
+ create_kw_opt_param("returning", type: returning_type, default: "nil"),
473
+ ]
474
+
475
+ # Bang methods don't have the `unique_by` parameter
476
+ unless bang_method?(method_name)
477
+ parameters << create_kw_opt_param("unique_by", type: unique_by_type, default: "nil")
478
+ end
479
+
480
+ association_relation_methods_module.create_method(
481
+ method_name.to_s,
482
+ parameters: parameters,
483
+ return_type: "ActiveRecord::Result"
484
+ )
485
+ when :proxy_association
486
+ # skip - private method
487
+ end
488
+ end
489
+ end
490
+
491
+ sig { params(common_relation_methods_module: RBI::Scope).void }
492
+ def create_common_methods(common_relation_methods_module)
493
+ create_common_method(
494
+ "destroy_all",
495
+ common_relation_methods_module,
496
+ return_type: "T::Array[#{constant_name}]"
497
+ )
498
+
499
+ FINDER_METHODS.each do |method_name|
500
+ case method_name
501
+ when :exists?
502
+ create_common_method(
503
+ "exists?",
504
+ common_relation_methods_module,
505
+ parameters: [
506
+ create_opt_param("conditions", type: "T.untyped", default: ":none"),
507
+ ],
508
+ return_type: "T::Boolean"
509
+ )
510
+ when :include?, :member?
511
+ create_common_method(
512
+ method_name,
513
+ common_relation_methods_module,
514
+ parameters: [
515
+ create_param("record", type: "T.untyped"),
516
+ ],
517
+ return_type: "T::Boolean"
518
+ )
519
+ when :find
520
+ create_common_method(
521
+ "find",
522
+ common_relation_methods_module,
523
+ parameters: [
524
+ create_rest_param("args", type: "T.untyped"),
525
+ ],
526
+ return_type: "T.untyped"
527
+ )
528
+ when :find_by
529
+ create_common_method(
530
+ "find_by",
531
+ common_relation_methods_module,
532
+ parameters: [
533
+ create_rest_param("args", type: "T.untyped"),
534
+ ],
535
+ return_type: as_nilable_type(constant_name)
536
+ )
537
+ when :find_by!
538
+ create_common_method(
539
+ "find_by!",
540
+ common_relation_methods_module,
541
+ parameters: [
542
+ create_rest_param("args", type: "T.untyped"),
543
+ ],
544
+ return_type: constant_name
545
+ )
546
+ when :first, :last, :take
547
+ create_common_method(
548
+ method_name,
549
+ common_relation_methods_module,
550
+ parameters: [
551
+ create_opt_param("limit", type: "T.untyped", default: "nil"),
552
+ ],
553
+ return_type: "T.untyped"
554
+ )
555
+ when :raise_record_not_found_exception!
556
+ # skip
557
+ else
558
+ return_type = if bang_method?(method_name)
559
+ constant_name
560
+ else
561
+ as_nilable_type(constant_name)
562
+ end
563
+
564
+ create_common_method(
565
+ method_name,
566
+ common_relation_methods_module,
567
+ return_type: return_type
568
+ )
569
+ end
570
+ end
571
+
572
+ CALCULATION_METHODS.each do |method_name|
573
+ case method_name
574
+ when :average, :maximum, :minimum
575
+ create_common_method(
576
+ method_name,
577
+ common_relation_methods_module,
578
+ parameters: [
579
+ create_param("column_name", type: "T.any(String, Symbol)"),
580
+ ],
581
+ return_type: "T.untyped"
582
+ )
583
+ when :calculate
584
+ create_common_method(
585
+ "calculate",
586
+ common_relation_methods_module,
587
+ parameters: [
588
+ create_param("operation", type: "Symbol"),
589
+ create_param("column_name", type: "T.any(String, Symbol)"),
590
+ ],
591
+ return_type: "T.untyped"
592
+ )
593
+ when :count
594
+ create_common_method(
595
+ "count",
596
+ common_relation_methods_module,
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", common_relation_methods_module, return_type: "Array")
604
+ when :pick, :pluck
605
+ create_common_method(
606
+ method_name,
607
+ common_relation_methods_module,
608
+ parameters: [
609
+ create_rest_param("column_names", type: "T.untyped"),
610
+ ],
611
+ return_type: "T.untyped"
612
+ )
613
+ when :sum
614
+ create_common_method(
615
+ "sum",
616
+ common_relation_methods_module,
617
+ parameters: [
618
+ create_opt_param("column_name", type: "T.nilable(T.any(String, Symbol))", default: "nil"),
619
+ create_block_param("block", type: "T.nilable(T.proc.params(record: T.untyped).returns(T.untyped))"),
620
+ ],
621
+ return_type: "T.untyped"
622
+ )
623
+ end
624
+ end
625
+
626
+ ENUMERABLE_QUERY_METHODS.each do |method_name|
627
+ block_type = "T.nilable(T.proc.params(record: #{constant_name}).returns(T.untyped))"
628
+ create_common_method(
629
+ method_name,
630
+ common_relation_methods_module,
631
+ parameters: [
632
+ create_block_param("block", type: block_type),
633
+ ],
634
+ return_type: "T::Boolean"
635
+ )
636
+ end
637
+
638
+ FIND_OR_CREATE_METHODS.each do |method_name|
639
+ block_type = "T.nilable(T.proc.params(object: #{constant_name}).void)"
640
+ create_common_method(
641
+ method_name,
642
+ common_relation_methods_module,
643
+ parameters: [
644
+ create_param("attributes", type: "T.untyped"),
645
+ create_block_param("block", type: block_type),
646
+ ],
647
+ return_type: constant_name
648
+ )
649
+ end
650
+
651
+ BUILDER_METHODS.each do |method_name|
652
+ create_common_method(
653
+ method_name,
654
+ common_relation_methods_module,
655
+ parameters: [
656
+ create_opt_param("attributes", type: "T.untyped", default: "nil"),
657
+ create_block_param("block", type: "T.nilable(T.proc.params(object: #{constant_name}).void)"),
658
+ ],
659
+ return_type: constant_name
660
+ )
661
+ end
662
+ end
663
+
664
+ sig do
665
+ params(
666
+ name: T.any(Symbol, String),
667
+ common_relation_methods_module: RBI::Scope,
668
+ parameters: T::Array[RBI::TypedParam],
669
+ return_type: T.nilable(String)
670
+ ).void
671
+ end
672
+ def create_common_method(name, common_relation_methods_module, parameters: [], return_type: nil)
673
+ common_relation_methods_module.create_method(
674
+ name.to_s,
675
+ parameters: parameters,
676
+ return_type: return_type || "void"
677
+ )
678
+ end
679
+
680
+ sig do
681
+ params(
682
+ name: T.any(Symbol, String),
683
+ relation_methods_module: RBI::Scope,
684
+ association_relation_methods_module: RBI::Scope,
685
+ parameters: T::Array[RBI::TypedParam],
686
+ relation_return_type: String,
687
+ association_return_type: String,
688
+ ).void
689
+ end
690
+ def create_relation_method(
691
+ name,
692
+ relation_methods_module,
693
+ association_relation_methods_module,
694
+ parameters: [],
695
+ relation_return_type: RelationClassName,
696
+ association_return_type: AssociationRelationClassName
697
+ )
698
+ relation_methods_module.create_method(
699
+ name.to_s,
700
+ parameters: parameters,
701
+ return_type: relation_return_type
702
+ )
703
+ association_relation_methods_module.create_method(
704
+ name.to_s,
705
+ parameters: parameters,
706
+ return_type: association_return_type
707
+ )
708
+ end
709
+ end
710
+ end
711
+ end
712
+ end