tapioca 0.4.27 → 0.5.3

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +15 -15
  3. data/README.md +2 -2
  4. data/Rakefile +5 -7
  5. data/exe/tapioca +2 -2
  6. data/lib/tapioca/cli.rb +172 -2
  7. data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
  8. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
  9. data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
  10. data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
  11. data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
  12. data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
  13. data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
  14. data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
  15. data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
  16. data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
  17. data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
  18. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
  19. data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
  20. data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
  21. data/lib/tapioca/compilers/dsl/active_support_concern.rb +106 -0
  22. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
  23. data/lib/tapioca/compilers/dsl/base.rb +108 -82
  24. data/lib/tapioca/compilers/dsl/config.rb +111 -0
  25. data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
  26. data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
  27. data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
  28. data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
  29. data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
  30. data/lib/tapioca/compilers/dsl/smart_properties.rb +21 -33
  31. data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
  32. data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
  33. data/lib/tapioca/compilers/dsl_compiler.rb +25 -40
  34. data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
  35. data/lib/tapioca/compilers/requires_compiler.rb +2 -2
  36. data/lib/tapioca/compilers/sorbet.rb +25 -5
  37. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +122 -206
  38. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
  39. data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
  40. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  41. data/lib/tapioca/config.rb +3 -0
  42. data/lib/tapioca/config_builder.rb +5 -2
  43. data/lib/tapioca/constant_locator.rb +6 -8
  44. data/lib/tapioca/gemfile.rb +14 -11
  45. data/lib/tapioca/generators/base.rb +61 -0
  46. data/lib/tapioca/generators/dsl.rb +362 -0
  47. data/lib/tapioca/generators/gem.rb +345 -0
  48. data/lib/tapioca/generators/init.rb +79 -0
  49. data/lib/tapioca/generators/require.rb +52 -0
  50. data/lib/tapioca/generators/todo.rb +76 -0
  51. data/lib/tapioca/generators.rb +9 -0
  52. data/lib/tapioca/generic_type_registry.rb +25 -98
  53. data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
  54. data/lib/tapioca/internal.rb +2 -10
  55. data/lib/tapioca/loader.rb +11 -31
  56. data/lib/tapioca/rbi_ext/model.rb +166 -0
  57. data/lib/tapioca/reflection.rb +138 -0
  58. data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
  59. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
  60. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  61. data/lib/tapioca/version.rb +1 -1
  62. data/lib/tapioca.rb +3 -0
  63. metadata +45 -23
  64. data/lib/tapioca/cli/main.rb +0 -146
  65. data/lib/tapioca/core_ext/class.rb +0 -28
  66. data/lib/tapioca/core_ext/string.rb +0 -18
  67. data/lib/tapioca/generator.rb +0 -633
  68. data/lib/tapioca/rbi/model.rb +0 -405
  69. data/lib/tapioca/rbi/printer.rb +0 -410
  70. data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
  71. data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
  72. data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
  73. data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
  74. data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -0,0 +1,131 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_model"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ module Tapioca
11
+ module Compilers
12
+ module Dsl
13
+ # `Tapioca::Compilers::Dsl::ActiveModelAttributes` decorates RBI files for all
14
+ # classes that use [`ActiveModel::Attributes`](https://edgeapi.rubyonrails.org/classes/ActiveModel/Attributes/ClassMethods.html).
15
+ #
16
+ # For example, with the following class:
17
+ #
18
+ # ~~~rb
19
+ # class Shop
20
+ # include ActiveModel::Attributes
21
+ #
22
+ # attribute :name, :string
23
+ # end
24
+ # ~~~
25
+ #
26
+ # this generator will produce an RBI file with the following content:
27
+ # ~~~rbi
28
+ # # typed: true
29
+ #
30
+ # class Shop
31
+ #
32
+ # sig { returns(T.nilable(::String)) }
33
+ # def name; end
34
+ #
35
+ # sig { params(name: T.nilable(::String)).returns(T.nilable(::String)) }
36
+ # def name=(name); end
37
+ # end
38
+ # ~~~
39
+ class ActiveModelAttributes < Base
40
+ extend T::Sig
41
+
42
+ sig { override.params(root: RBI::Tree, constant: T.all(Class, ::ActiveModel::Attributes::ClassMethods)).void }
43
+ def decorate(root, constant)
44
+ attribute_methods = attribute_methods_for(constant)
45
+ return if attribute_methods.empty?
46
+
47
+ root.create_path(constant) do |klass|
48
+ attribute_methods.each do |method, attribute_type|
49
+ generate_method(klass, method, attribute_type)
50
+ end
51
+ end
52
+ end
53
+
54
+ sig { override.returns(T::Enumerable[Module]) }
55
+ def gather_constants
56
+ all_classes.grep(::ActiveModel::Attributes::ClassMethods)
57
+ end
58
+
59
+ private
60
+
61
+ HANDLED_METHOD_TARGETS = T.let(["attribute", "attribute="], T::Array[String])
62
+
63
+ sig { params(constant: ::ActiveModel::Attributes::ClassMethods).returns(T::Array[[::String, ::String]]) }
64
+ def attribute_methods_for(constant)
65
+ constant.attribute_method_matchers.flat_map do |matcher|
66
+ constant.attribute_types.map do |name, value|
67
+ next unless handle_method_matcher?(matcher)
68
+
69
+ [matcher.method_name(name), type_for(value)]
70
+ end.compact
71
+ end
72
+ end
73
+
74
+ sig do
75
+ params(matcher: ::ActiveModel::AttributeMethods::ClassMethods::AttributeMethodMatcher)
76
+ .returns(T::Boolean)
77
+ end
78
+ def handle_method_matcher?(matcher)
79
+ target = if matcher.respond_to?(:method_missing_target)
80
+ # Pre-Rails 6.0, the field is named "method_missing_target"
81
+ T.unsafe(matcher).method_missing_target
82
+ else
83
+ # Rails 6.0+ has renamed the field to "target"
84
+ matcher.target
85
+ end
86
+
87
+ HANDLED_METHOD_TARGETS.include?(target.to_s)
88
+ end
89
+
90
+ sig { params(attribute_type_value: ::ActiveModel::Type::Value).returns(::String) }
91
+ def type_for(attribute_type_value)
92
+ type = case attribute_type_value
93
+ when ActiveModel::Type::Boolean
94
+ "T::Boolean"
95
+ when ActiveModel::Type::Date
96
+ "::Date"
97
+ when ActiveModel::Type::DateTime, ActiveModel::Type::Time
98
+ "::DateTime"
99
+ when ActiveModel::Type::Decimal
100
+ "::BigDecimal"
101
+ when ActiveModel::Type::Float
102
+ "::Float"
103
+ when ActiveModel::Type::Integer
104
+ "::Integer"
105
+ when ActiveModel::Type::String
106
+ "::String"
107
+ else
108
+ # we don't want untyped to be wrapped by T.nilable, so just return early
109
+ return "T.untyped"
110
+ end
111
+
112
+ "T.nilable(#{type})"
113
+ end
114
+
115
+ sig { params(klass: RBI::Scope, method: String, type: String).void }
116
+ def generate_method(klass, method, type)
117
+ if method.end_with?("=")
118
+ parameter = create_param("value", type: type)
119
+ klass.create_method(
120
+ method,
121
+ parameters: [parameter],
122
+ return_type: type
123
+ )
124
+ else
125
+ klass.create_method(method, return_type: type)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,101 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "active_model"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ module Tapioca
11
+ module Compilers
12
+ module Dsl
13
+ # `Tapioca::Compilers::Dsl::ActiveModelSecurePassword` decorates RBI files for all
14
+ # classes that use [`ActiveModel::SecurePassword`](http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html).
15
+ #
16
+ # For example, with the following class:
17
+ #
18
+ # ~~~rb
19
+ # class User
20
+ # include ActiveModel::SecurePassword
21
+ #
22
+ # has_secure_password
23
+ # has_secure_password :token
24
+ # end
25
+ # ~~~
26
+ #
27
+ # this generator will produce an RBI file with the following content:
28
+ # ~~~rbi
29
+ # # typed: true
30
+ #
31
+ # class User
32
+ # sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
33
+ # def authenticate(unencrypted_password); end
34
+ #
35
+ # sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
36
+ # def authenticate_password(unencrypted_password); end
37
+ #
38
+ # sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
39
+ # def authenticate_token(unencrypted_password); end
40
+ #
41
+ # sig { returns(T.untyped) }
42
+ # def password; end
43
+ #
44
+ # sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
45
+ # def password=(unencrypted_password); end
46
+ #
47
+ # sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
48
+ # def password_confirmation=(unencrypted_password); end
49
+ #
50
+ # sig { returns(T.untyped) }
51
+ # def token; end
52
+ #
53
+ # sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
54
+ # def token=(unencrypted_password); end
55
+ #
56
+ # sig { params(unencrypted_password: T.untyped).returns(T.untyped) }
57
+ # def token_confirmation=(unencrypted_password); end
58
+ # end
59
+ # ~~~
60
+ class ActiveModelSecurePassword < Base
61
+ extend T::Sig
62
+
63
+ sig do
64
+ override
65
+ .params(root: RBI::Tree, constant: T.all(Class, ::ActiveModel::SecurePassword::ClassMethods))
66
+ .void
67
+ end
68
+ def decorate(root, constant)
69
+ instance_methods_modules = if constant < ActiveModel::SecurePassword::InstanceMethodsOnActivation
70
+ # pre Rails 6.0, this used to be a single static module
71
+ [ActiveModel::SecurePassword::InstanceMethodsOnActivation]
72
+ else
73
+ # post Rails 6.0, this is now using a dynmaic module builder pattern
74
+ # and we can have multiple different ones included into the model
75
+ constant.ancestors.grep(ActiveModel::SecurePassword::InstanceMethodsOnActivation)
76
+ end
77
+
78
+ return if instance_methods_modules.empty?
79
+
80
+ methods = instance_methods_modules.flat_map { |mod| mod.instance_methods(false) }
81
+ return if methods.empty?
82
+
83
+ root.create_path(constant) do |klass|
84
+ methods.each do |method|
85
+ create_method_from_def(klass, constant.instance_method(method))
86
+ end
87
+ end
88
+ end
89
+
90
+ sig { override.returns(T::Enumerable[Module]) }
91
+ def gather_constants
92
+ # This selects all classes that are `ActiveModel::SecurePassword::ClassMethods === klass`.
93
+ # In other words, we select all classes that have `ActiveModel::SecurePassword::ClassMethods`
94
+ # as an ancestor of its singleton class, i.e. all classes that have extended the
95
+ # `ActiveModel::SecurePassword::ClassMethods` module.
96
+ all_classes.grep(::ActiveModel::SecurePassword::ClassMethods)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
-
6
4
  begin
7
5
  require "active_record"
8
6
  rescue LoadError
@@ -70,7 +68,7 @@ module Tapioca
70
68
  # sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
71
69
  # def comment_ids=(ids); end
72
70
  #
73
- # sig { returns(::ActiveRecord::Associations::CollectionProxy[Comment]) }
71
+ # sig { returns(::ActiveRecord::Associations::CollectionProxy[::Comment]) }
74
72
  # def comments; end
75
73
  #
76
74
  # sig { params(value: T::Enumerable[::Comment]).void }
@@ -106,11 +104,11 @@ module Tapioca
106
104
  T.any(::ActiveRecord::Reflection::ThroughReflection, ::ActiveRecord::Reflection::AssociationReflection)
107
105
  end
108
106
 
109
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
107
+ sig { override.params(root: RBI::Tree, constant: T.class_of(ActiveRecord::Base)).void }
110
108
  def decorate(root, constant)
111
109
  return if constant.reflections.empty?
112
110
 
113
- root.path(constant) do |model|
111
+ root.create_path(constant) do |model|
114
112
  module_name = "GeneratedAssociationMethods"
115
113
 
116
114
  model.create_module(module_name) do |mod|
@@ -124,26 +122,23 @@ module Tapioca
124
122
 
125
123
  sig { override.returns(T::Enumerable[Module]) }
126
124
  def gather_constants
127
- ActiveRecord::Base.descendants.reject(&:abstract_class?)
125
+ descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
128
126
  end
129
127
 
130
128
  private
131
129
 
132
- sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
130
+ sig { params(mod: RBI::Scope, constant: T.class_of(ActiveRecord::Base)).void }
133
131
  def populate_nested_attribute_writers(mod, constant)
134
132
  constant.nested_attributes_options.keys.each do |association_name|
135
- create_method(
136
- mod,
133
+ mod.create_method(
137
134
  "#{association_name}_attributes=",
138
- parameters: [
139
- Parlour::RbiGenerator::Parameter.new("attributes", type: "T.untyped"),
140
- ],
135
+ parameters: [create_param("attributes", type: "T.untyped")],
141
136
  return_type: "T.untyped"
142
137
  )
143
138
  end
144
139
  end
145
140
 
146
- sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
141
+ sig { params(mod: RBI::Scope, constant: T.class_of(ActiveRecord::Base)).void }
147
142
  def populate_associations(mod, constant)
148
143
  constant.reflections.each do |association_name, reflection|
149
144
  if reflection.collection?
@@ -156,7 +151,7 @@ module Tapioca
156
151
 
157
152
  sig do
158
153
  params(
159
- klass: Parlour::RbiGenerator::Namespace,
154
+ klass: RBI::Scope,
160
155
  constant: T.class_of(ActiveRecord::Base),
161
156
  association_name: T.any(String, Symbol),
162
157
  reflection: ReflectionType
@@ -166,49 +161,41 @@ module Tapioca
166
161
  association_class = type_for(constant, reflection)
167
162
  association_type = "T.nilable(#{association_class})"
168
163
 
169
- create_method(
170
- klass,
164
+ klass.create_method(
171
165
  association_name.to_s,
172
166
  return_type: association_type,
173
167
  )
174
- create_method(
175
- klass,
168
+ klass.create_method(
176
169
  "#{association_name}=",
177
- parameters: [
178
- Parlour::RbiGenerator::Parameter.new("value", type: association_type),
179
- ],
180
- return_type: nil
170
+ parameters: [create_param("value", type: association_type)],
171
+ return_type: "void"
181
172
  )
182
- create_method(
183
- klass,
173
+ klass.create_method(
184
174
  "reload_#{association_name}",
185
175
  return_type: association_type,
186
176
  )
187
177
  unless reflection.polymorphic?
188
- create_method(
189
- klass,
178
+ klass.create_method(
190
179
  "build_#{association_name}",
191
180
  parameters: [
192
- Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
193
- Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
181
+ create_rest_param("args", type: "T.untyped"),
182
+ create_block_param("blk", type: "T.untyped"),
194
183
  ],
195
184
  return_type: association_class
196
185
  )
197
- create_method(
198
- klass,
186
+ klass.create_method(
199
187
  "create_#{association_name}",
200
188
  parameters: [
201
- Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
202
- Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
189
+ create_rest_param("args", type: "T.untyped"),
190
+ create_block_param("blk", type: "T.untyped"),
203
191
  ],
204
192
  return_type: association_class
205
193
  )
206
- create_method(
207
- klass,
194
+ klass.create_method(
208
195
  "create_#{association_name}!",
209
196
  parameters: [
210
- Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
211
- Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
197
+ create_rest_param("args", type: "T.untyped"),
198
+ create_block_param("blk", type: "T.untyped"),
212
199
  ],
213
200
  return_type: association_class
214
201
  )
@@ -217,7 +204,7 @@ module Tapioca
217
204
 
218
205
  sig do
219
206
  params(
220
- klass: Parlour::RbiGenerator::Namespace,
207
+ klass: RBI::Scope,
221
208
  constant: T.class_of(ActiveRecord::Base),
222
209
  association_name: T.any(String, Symbol),
223
210
  reflection: ReflectionType
@@ -227,30 +214,22 @@ module Tapioca
227
214
  association_class = type_for(constant, reflection)
228
215
  relation_class = relation_type_for(constant, reflection)
229
216
 
230
- create_method(
231
- klass,
217
+ klass.create_method(
232
218
  association_name.to_s,
233
219
  return_type: relation_class,
234
220
  )
235
- create_method(
236
- klass,
221
+ klass.create_method(
237
222
  "#{association_name}=",
238
- parameters: [
239
- Parlour::RbiGenerator::Parameter.new("value", type: "T::Enumerable[#{association_class}]"),
240
- ],
241
- return_type: nil,
223
+ parameters: [create_param("value", type: "T::Enumerable[#{association_class}]")],
224
+ return_type: "void",
242
225
  )
243
- create_method(
244
- klass,
226
+ klass.create_method(
245
227
  "#{association_name.to_s.singularize}_ids",
246
228
  return_type: "T::Array[T.untyped]"
247
229
  )
248
- create_method(
249
- klass,
230
+ klass.create_method(
250
231
  "#{association_name.to_s.singularize}_ids=",
251
- parameters: [
252
- Parlour::RbiGenerator::Parameter.new("ids", type: "T::Array[T.untyped]"),
253
- ],
232
+ parameters: [create_param("ids", type: "T::Array[T.untyped]")],
254
233
  return_type: "T::Array[T.untyped]"
255
234
  )
256
235
  end
@@ -264,7 +243,7 @@ module Tapioca
264
243
  def type_for(constant, reflection)
265
244
  return "T.untyped" if !constant.table_exists? || polymorphic_association?(reflection)
266
245
 
267
- "::#{reflection.klass.name}"
246
+ T.must(qualified_name_of(reflection.klass))
268
247
  end
269
248
 
270
249
  sig do
@@ -278,7 +257,7 @@ module Tapioca
278
257
  polymorphic_association?(reflection)
279
258
 
280
259
  # Change to: "::#{reflection.klass.name}::ActiveRecord_Associations_CollectionProxy"
281
- "::ActiveRecord::Associations::CollectionProxy[#{reflection.klass.name}]"
260
+ "::ActiveRecord::Associations::CollectionProxy[#{qualified_name_of(reflection.klass)}]"
282
261
  end
283
262
 
284
263
  sig do
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
-
6
4
  begin
7
5
  require "active_record"
8
6
  rescue LoadError
@@ -99,11 +97,11 @@ module Tapioca
99
97
  class ActiveRecordColumns < Base
100
98
  extend T::Sig
101
99
 
102
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
100
+ sig { override.params(root: RBI::Tree, constant: T.class_of(ActiveRecord::Base)).void }
103
101
  def decorate(root, constant)
104
102
  return unless constant.table_exists?
105
103
 
106
- root.path(constant) do |model|
104
+ root.create_path(constant) do |model|
107
105
  module_name = "GeneratedAttributeMethods"
108
106
 
109
107
  model.create_module(module_name) do |mod|
@@ -129,34 +127,31 @@ module Tapioca
129
127
 
130
128
  sig { override.returns(T::Enumerable[Module]) }
131
129
  def gather_constants
132
- ActiveRecord::Base.descendants.reject(&:abstract_class?)
130
+ descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
133
131
  end
134
132
 
135
133
  private
136
134
 
137
135
  sig do
138
136
  params(
139
- klass: Parlour::RbiGenerator::Namespace,
137
+ klass: RBI::Scope,
140
138
  name: String,
141
139
  methods_to_add: T.nilable(T::Array[String]),
142
- return_type: T.nilable(String),
140
+ return_type: String,
143
141
  parameters: T::Array[[String, String]]
144
142
  ).void
145
143
  end
146
- def add_method(klass, name, methods_to_add, return_type: nil, parameters: [])
147
- create_method(
148
- klass,
144
+ def add_method(klass, name, methods_to_add, return_type: "void", parameters: [])
145
+ klass.create_method(
149
146
  name,
150
- parameters: parameters.map do |param, type|
151
- Parlour::RbiGenerator::Parameter.new(param, type: type)
152
- end,
147
+ parameters: parameters.map { |param, type| create_param(param, type: type) },
153
148
  return_type: return_type
154
149
  ) if methods_to_add.nil? || methods_to_add.include?(name)
155
150
  end
156
151
 
157
152
  sig do
158
153
  params(
159
- klass: Parlour::RbiGenerator::Namespace,
154
+ klass: RBI::Scope,
160
155
  constant: T.class_of(ActiveRecord::Base),
161
156
  column_name: String,
162
157
  attribute_name: String,
@@ -164,7 +159,7 @@ module Tapioca
164
159
  ).void
165
160
  end
166
161
  def add_methods_for_attribute(klass, constant, column_name, attribute_name = column_name, methods_to_add = nil)
167
- getter_type, setter_type = type_for(constant, column_name)
162
+ getter_type, setter_type = ActiveRecordColumnTypeHelper.new(constant).type_for(column_name)
168
163
 
169
164
  # Added by ActiveRecord::AttributeMethods::Read
170
165
  #
@@ -298,96 +293,6 @@ module Tapioca
298
293
  )
299
294
  end
300
295
 
301
- sig do
302
- params(
303
- constant: T.class_of(ActiveRecord::Base),
304
- column_name: String
305
- ).returns([String, String])
306
- end
307
- def type_for(constant, column_name)
308
- return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(constant)
309
-
310
- column_type = constant.attribute_types[column_name]
311
-
312
- getter_type =
313
- case column_type
314
- when defined?(MoneyColumn) && MoneyColumn::ActiveRecordType
315
- "::Money"
316
- when ActiveRecord::Type::Integer
317
- "::Integer"
318
- when ActiveRecord::Type::String
319
- "::String"
320
- when ActiveRecord::Type::Date
321
- "::Date"
322
- when ActiveRecord::Type::Decimal
323
- "::BigDecimal"
324
- when ActiveRecord::Type::Float
325
- "::Float"
326
- when ActiveRecord::Type::Boolean
327
- "T::Boolean"
328
- when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
329
- "::DateTime"
330
- when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
331
- "::ActiveSupport::TimeWithZone"
332
- else
333
- handle_unknown_type(column_type)
334
- end
335
-
336
- column = constant.columns_hash[column_name]
337
- setter_type = getter_type
338
-
339
- if column&.null
340
- return ["T.nilable(#{getter_type})", "T.nilable(#{setter_type})"]
341
- end
342
-
343
- if column_name == constant.primary_key ||
344
- column_name == "created_at" ||
345
- column_name == "updated_at"
346
- getter_type = "T.nilable(#{getter_type})"
347
- end
348
-
349
- [getter_type, setter_type]
350
- end
351
-
352
- sig { params(constant: Module).returns(T::Boolean) }
353
- def do_not_generate_strong_types?(constant)
354
- Object.const_defined?(:StrongTypeGeneration) &&
355
- !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
356
- end
357
-
358
- sig { params(column_type: Object).returns(String) }
359
- def handle_unknown_type(column_type)
360
- return "T.untyped" unless ActiveModel::Type::Value === column_type
361
-
362
- lookup_return_type_of_method(column_type, :deserialize) ||
363
- lookup_return_type_of_method(column_type, :cast) ||
364
- lookup_arg_type_of_method(column_type, :serialize) ||
365
- "T.untyped"
366
- end
367
-
368
- sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
369
- def lookup_return_type_of_method(column_type, method)
370
- signature = T::Private::Methods.signature_for_method(column_type.method(method))
371
- return unless signature
372
-
373
- return_type = signature.return_type
374
- return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
375
-
376
- return_type.to_s
377
- end
378
-
379
- sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
380
- def lookup_arg_type_of_method(column_type, method)
381
- signature = T::Private::Methods.signature_for_method(column_type.method(method))
382
- return unless signature
383
-
384
- # Arg types is an array [name, type] entries, so we desctructure the type of
385
- # first argument to get the first argument type
386
- _, first_argument_type = signature.arg_types.first
387
-
388
- first_argument_type.to_s
389
- end
390
-
391
296
  sig { params(type: String).returns(String) }
392
297
  def as_nilable_type(type)
393
298
  return type if type.start_with?("T.nilable(")
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "parlour"
5
-
6
4
  begin
7
5
  require "active_record"
8
6
  rescue LoadError
@@ -60,11 +58,11 @@ module Tapioca
60
58
  class ActiveRecordEnum < Base
61
59
  extend T::Sig
62
60
 
63
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::ActiveRecord::Base)).void }
61
+ sig { override.params(root: RBI::Tree, constant: T.class_of(::ActiveRecord::Base)).void }
64
62
  def decorate(root, constant)
65
63
  return if constant.defined_enums.empty?
66
64
 
67
- root.path(constant) do |model|
65
+ root.create_path(constant) do |model|
68
66
  module_name = "EnumMethodsModule"
69
67
 
70
68
  model.create_module(module_name) do |mod|
@@ -75,14 +73,14 @@ module Tapioca
75
73
 
76
74
  constant.defined_enums.each do |name, enum_map|
77
75
  type = type_for_enum(enum_map)
78
- create_method(model, name.pluralize, class_method: true, return_type: type)
76
+ model.create_method(name.pluralize, class_method: true, return_type: type)
79
77
  end
80
78
  end
81
79
  end
82
80
 
83
81
  sig { override.returns(T::Enumerable[Module]) }
84
82
  def gather_constants
85
- ::ActiveRecord::Base.descendants.reject(&:abstract_class?)
83
+ descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
86
84
  end
87
85
 
88
86
  private
@@ -93,21 +91,21 @@ module Tapioca
93
91
  value_type = if value_type.length == 1
94
92
  value_type.first
95
93
  else
96
- "T.any(#{value_type.join(', ')})"
94
+ "T.any(#{value_type.join(", ")})"
97
95
  end
98
96
 
99
97
  "T::Hash[T.any(String, Symbol), #{value_type}]"
100
98
  end
101
99
 
102
- sig { params(constant: T.class_of(::ActiveRecord::Base), klass: Parlour::RbiGenerator::Namespace).void }
100
+ sig { params(constant: T.class_of(::ActiveRecord::Base), klass: RBI::Scope).void }
103
101
  def generate_instance_methods(constant, klass)
104
102
  methods = constant.send(:_enum_methods_module).instance_methods
105
103
 
106
104
  methods.each do |method|
107
105
  method = method.to_s
108
- return_type = "T::Boolean" if method.end_with?("?")
106
+ return_type = method.end_with?("?") ? "T::Boolean" : "void"
109
107
 
110
- create_method(klass, method, return_type: return_type)
108
+ klass.create_method(method, return_type: return_type)
111
109
  end
112
110
  end
113
111
  end