tapioca 0.6.4 → 0.7.2

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.
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
@@ -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_type_variable("Elem", type: "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_type_variable("Elem", type: "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_type_variable("Elem", type: "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_type_variable("Elem", type: "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_type_variable("Elem", type: "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