tapioca 0.2.7 → 0.4.1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +27 -1
  3. data/README.md +21 -2
  4. data/Rakefile +15 -4
  5. data/lib/tapioca.rb +15 -9
  6. data/lib/tapioca/cli.rb +41 -12
  7. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +129 -0
  8. data/lib/tapioca/compilers/dsl/action_mailer.rb +65 -0
  9. data/lib/tapioca/compilers/dsl/active_record_associations.rb +285 -0
  10. data/lib/tapioca/compilers/dsl/active_record_columns.rb +379 -0
  11. data/lib/tapioca/compilers/dsl/active_record_enum.rb +112 -0
  12. data/lib/tapioca/compilers/dsl/active_record_identity_cache.rb +213 -0
  13. data/lib/tapioca/compilers/dsl/active_record_scope.rb +100 -0
  14. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +170 -0
  15. data/lib/tapioca/compilers/dsl/active_resource.rb +140 -0
  16. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +126 -0
  17. data/lib/tapioca/compilers/dsl/base.rb +163 -0
  18. data/lib/tapioca/compilers/dsl/frozen_record.rb +96 -0
  19. data/lib/tapioca/compilers/dsl/protobuf.rb +144 -0
  20. data/lib/tapioca/compilers/dsl/smart_properties.rb +173 -0
  21. data/lib/tapioca/compilers/dsl/state_machines.rb +378 -0
  22. data/lib/tapioca/compilers/dsl/url_helpers.rb +83 -0
  23. data/lib/tapioca/compilers/dsl_compiler.rb +121 -0
  24. data/lib/tapioca/compilers/requires_compiler.rb +67 -0
  25. data/lib/tapioca/compilers/sorbet.rb +34 -0
  26. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +209 -49
  27. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +3 -17
  28. data/lib/tapioca/compilers/todos_compiler.rb +32 -0
  29. data/lib/tapioca/config.rb +42 -0
  30. data/lib/tapioca/config_builder.rb +75 -0
  31. data/lib/tapioca/constant_locator.rb +1 -0
  32. data/lib/tapioca/core_ext/class.rb +23 -0
  33. data/lib/tapioca/gemfile.rb +14 -1
  34. data/lib/tapioca/generator.rb +235 -67
  35. data/lib/tapioca/loader.rb +20 -9
  36. data/lib/tapioca/sorbet_config_parser.rb +77 -0
  37. data/lib/tapioca/version.rb +1 -1
  38. metadata +35 -66
@@ -0,0 +1,285 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "parlour"
5
+
6
+ begin
7
+ require "active_record"
8
+ rescue LoadError
9
+ return
10
+ end
11
+
12
+ module Tapioca
13
+ module Compilers
14
+ module Dsl
15
+ # `Tapioca::Compilers::Dsl::ActiveRecordAssociations` refines RBI files for subclasses of `ActiveRecord::Base`
16
+ # (see https://api.rubyonrails.org/classes/ActiveRecord/Base.html). This generator is only
17
+ # responsible for defining the methods that would be created for the association that
18
+ # are defined in the Active Record model.
19
+ #
20
+ # For example, with the following model class:
21
+ #
22
+ # ~~~rb
23
+ # class Post < ActiveRecord::Base
24
+ # belongs_to :category
25
+ # has_many :comments
26
+ # has_one :author, class_name: "User"
27
+ # end
28
+ # ~~~
29
+ #
30
+ # this generator will produce the following methods in the RBI file
31
+ # `post.rbi`:
32
+ #
33
+ # ~~~rbi
34
+ # # post.rbi
35
+ # # typed: true
36
+ #
37
+ # class Post
38
+ # include Post::GeneratedAssociationMethods
39
+ # end
40
+ #
41
+ # module Post::GeneratedAssociationMethods
42
+ # sig { returns(T.nilable(::User)) }
43
+ # def author; end
44
+ #
45
+ # sig { params(value: T.nilable(::User)).void }
46
+ # def author=(value); end
47
+ #
48
+ # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::User)) }
49
+ # def build_author(*args, &blk); end
50
+ #
51
+ # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::Category)) }
52
+ # def build_category(*args, &blk); end
53
+ #
54
+ # sig { returns(T.nilable(::Category)) }
55
+ # def category; end
56
+ #
57
+ # sig { params(value: T.nilable(::Category)).void }
58
+ # def category=(value); end
59
+ #
60
+ # sig { returns(T::Array[T.untyped]) }
61
+ # def comment_ids; end
62
+ #
63
+ # sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
64
+ # def comment_ids=(ids); end
65
+ #
66
+ # sig { returns(::ActiveRecord::Associations::CollectionProxy[Comment]) }
67
+ # def comments; end
68
+ #
69
+ # sig { params(value: T::Enumerable[::Comment]).void }
70
+ # def comments=(value); end
71
+ #
72
+ # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::User)) }
73
+ # def create_author(*args, &blk); end
74
+ #
75
+ # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::User)) }
76
+ # def create_author!(*args, &blk); end
77
+ #
78
+ # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::Category)) }
79
+ # def create_category(*args, &blk); end
80
+ #
81
+ # sig { params(args: T.untyped, blk: T.untyped).returns(T.nilable(::Category)) }
82
+ # def create_category!(*args, &blk); end
83
+ #
84
+ # sig { returns(T.nilable(::User)) }
85
+ # def reload_author; end
86
+ #
87
+ # sig { returns(T.nilable(::Category)) }
88
+ # def reload_category; end
89
+ # end
90
+ # ~~~
91
+ class ActiveRecordAssociations < Base
92
+ extend T::Sig
93
+
94
+ ReflectionType = T.type_alias do
95
+ T.any(::ActiveRecord::Reflection::ThroughReflection, ::ActiveRecord::Reflection::AssociationReflection)
96
+ end
97
+
98
+ sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
99
+ def decorate(root, constant)
100
+ return if constant.reflections.empty?
101
+
102
+ module_name = "#{constant}::GeneratedAssociationMethods"
103
+ root.create_module(module_name) do |mod|
104
+ constant.reflections.each do |association_name, reflection|
105
+ if reflection.collection?
106
+ populate_collection_assoc_getter_setter(mod, constant, association_name, reflection)
107
+ else
108
+ populate_single_assoc_getter_setter(mod, constant, association_name, reflection)
109
+ end
110
+ end
111
+ end
112
+
113
+ root.path(constant) do |klass|
114
+ klass.create_include(module_name)
115
+ end
116
+ end
117
+
118
+ sig { override.returns(T::Enumerable[Module]) }
119
+ def gather_constants
120
+ ActiveRecord::Base.descendants.reject(&:abstract_class?)
121
+ end
122
+
123
+ private
124
+
125
+ sig do
126
+ params(
127
+ klass: Parlour::RbiGenerator::Namespace,
128
+ constant: T.class_of(ActiveRecord::Base),
129
+ association_name: T.any(String, Symbol),
130
+ reflection: ReflectionType
131
+ ).void
132
+ end
133
+ def populate_single_assoc_getter_setter(klass, constant, association_name, reflection)
134
+ association_class = type_for(constant, reflection)
135
+ association_type = if belongs_to_and_required?(constant, reflection)
136
+ association_class
137
+ else
138
+ "T.nilable(#{association_class})"
139
+ end
140
+
141
+ create_method(
142
+ klass,
143
+ association_name.to_s,
144
+ return_type: association_type,
145
+ )
146
+ create_method(
147
+ klass,
148
+ "#{association_name}=",
149
+ parameters: [
150
+ Parlour::RbiGenerator::Parameter.new("value", type: association_type),
151
+ ],
152
+ return_type: nil
153
+ )
154
+ create_method(
155
+ klass,
156
+ "reload_#{association_name}",
157
+ return_type: association_type,
158
+ )
159
+ if reflection.constructable?
160
+ create_method(
161
+ klass,
162
+ "build_#{association_name}",
163
+ parameters: [
164
+ Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
165
+ Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
166
+ ],
167
+ return_type: association_type
168
+ )
169
+ create_method(
170
+ klass,
171
+ "create_#{association_name}",
172
+ parameters: [
173
+ Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
174
+ Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
175
+ ],
176
+ return_type: association_type
177
+ )
178
+ create_method(
179
+ klass,
180
+ "create_#{association_name}!",
181
+ parameters: [
182
+ Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
183
+ Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
184
+ ],
185
+ return_type: association_type
186
+ )
187
+ end
188
+ end
189
+
190
+ sig do
191
+ params(
192
+ klass: Parlour::RbiGenerator::Namespace,
193
+ constant: T.class_of(ActiveRecord::Base),
194
+ association_name: T.any(String, Symbol),
195
+ reflection: ReflectionType
196
+ ).void
197
+ end
198
+ def populate_collection_assoc_getter_setter(klass, constant, association_name, reflection)
199
+ association_class = type_for(constant, reflection)
200
+ relation_class = relation_type_for(constant, reflection)
201
+
202
+ create_method(
203
+ klass,
204
+ association_name.to_s,
205
+ return_type: relation_class,
206
+ )
207
+ create_method(
208
+ klass,
209
+ "#{association_name}=",
210
+ parameters: [
211
+ Parlour::RbiGenerator::Parameter.new("value", type: "T::Enumerable[#{association_class}]"),
212
+ ],
213
+ return_type: nil,
214
+ )
215
+ create_method(
216
+ klass,
217
+ "#{association_name.to_s.singularize}_ids",
218
+ return_type: "T::Array[T.untyped]"
219
+ )
220
+ create_method(
221
+ klass,
222
+ "#{association_name.to_s.singularize}_ids=",
223
+ parameters: [
224
+ Parlour::RbiGenerator::Parameter.new("ids", type: "T::Array[T.untyped]"),
225
+ ],
226
+ return_type: "T::Array[T.untyped]"
227
+ )
228
+ end
229
+
230
+ sig do
231
+ params(
232
+ constant: T.class_of(ActiveRecord::Base),
233
+ reflection: ReflectionType
234
+ ).returns(T::Boolean)
235
+ end
236
+ def belongs_to_and_required?(constant, reflection)
237
+ return false unless constant.table_exists?
238
+ return false unless reflection.belongs_to?
239
+ column_definition = constant.columns_hash[reflection.foreign_key.to_s]
240
+
241
+ !column_definition.nil? && !column_definition.null
242
+ end
243
+
244
+ sig do
245
+ params(
246
+ constant: T.class_of(ActiveRecord::Base),
247
+ reflection: ReflectionType
248
+ ).returns(String)
249
+ end
250
+ def type_for(constant, reflection)
251
+ return "T.untyped" if !constant.table_exists? || polymorphic_association?(reflection)
252
+
253
+ "::#{reflection.klass.name}"
254
+ end
255
+
256
+ sig do
257
+ params(
258
+ constant: T.class_of(ActiveRecord::Base),
259
+ reflection: ReflectionType
260
+ ).returns(String)
261
+ end
262
+ def relation_type_for(constant, reflection)
263
+ "ActiveRecord::Associations::CollectionProxy" if !constant.table_exists? ||
264
+ polymorphic_association?(reflection)
265
+
266
+ # Change to: "::#{reflection.klass.name}::ActiveRecord_Associations_CollectionProxy"
267
+ "::ActiveRecord::Associations::CollectionProxy[#{reflection.klass.name}]"
268
+ end
269
+
270
+ sig do
271
+ params(
272
+ reflection: ReflectionType
273
+ ).returns(T::Boolean)
274
+ end
275
+ def polymorphic_association?(reflection)
276
+ if reflection.through_reflection?
277
+ polymorphic_association?(reflection.source_reflection)
278
+ else
279
+ !!reflection.polymorphic?
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,379 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "parlour"
5
+
6
+ begin
7
+ require "active_record"
8
+ rescue LoadError
9
+ return
10
+ end
11
+
12
+ module Tapioca
13
+ module Compilers
14
+ module Dsl
15
+ # `Tapioca::Compilers::Dsl::ActiveRecordColumns` refines RBI files for subclasses of `ActiveRecord::Base`
16
+ # (see https://api.rubyonrails.org/classes/ActiveRecord/Base.html). This generator is only
17
+ # responsible for defining the attribute methods that would be created for the columns that
18
+ # are defined in the Active Record model.
19
+ #
20
+ # For example, with the following model class:
21
+ #
22
+ # ~~~rb
23
+ # class Post < ActiveRecord::Base
24
+ # end
25
+ # ~~~
26
+ #
27
+ # and the following database schema:
28
+ #
29
+ # ~~~rb
30
+ # # db/schema.rb
31
+ # create_table :posts do |t|
32
+ # t.string :title, null: false
33
+ # t.string :body
34
+ # t.boolean :published
35
+ # t.timestamps
36
+ # end
37
+ # ~~~
38
+ #
39
+ # this generator will produce the following methods in the RBI file
40
+ # `post.rbi`:
41
+ #
42
+ # ~~~rbi
43
+ # # post.rbi
44
+ # # typed: true
45
+ # class Post
46
+ # sig { returns(T.nilable(::String)) }
47
+ # def body; end
48
+ #
49
+ # sig { params(value: T.nilable(::String)).returns(T.nilable(::String)) }
50
+ # def body=; end
51
+ #
52
+ # sig { params(args: T.untyped).returns(T::Boolean) }
53
+ # def body?; end
54
+ #
55
+ # sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
56
+ # def created_at; end
57
+ #
58
+ # sig { params(value: ::ActiveSupport::TimeWithZone).returns(::ActiveSupport::TimeWithZone) }
59
+ # def created_at=; end
60
+ #
61
+ # sig { params(args: T.untyped).returns(T::Boolean) }
62
+ # def created_at?; end
63
+ #
64
+ # sig { returns(T.nilable(T::Boolean)) }
65
+ # def published; end
66
+ #
67
+ # sig { params(value: T::Boolean).returns(T::Boolean) }
68
+ # def published=; end
69
+ #
70
+ # sig { params(args: T.untyped).returns(T::Boolean) }
71
+ # def published?; end
72
+ #
73
+ # sig { returns(::String) }
74
+ # def title; end
75
+ #
76
+ # sig { params(value: ::String).returns(::String) }
77
+ # def title=(value); end
78
+ #
79
+ # sig { params(args: T.untyped).returns(T::Boolean) }
80
+ # def title?(*args); end
81
+ #
82
+ # sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
83
+ # def updated_at; end
84
+ #
85
+ # sig { params(value: ::ActiveSupport::TimeWithZone).returns(::ActiveSupport::TimeWithZone) }
86
+ # def updated_at=; end
87
+ #
88
+ # sig { params(args: T.untyped).returns(T::Boolean) }
89
+ # def updated_at?; end
90
+ #
91
+ # ## Also the methods added by https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html
92
+ # ## Also the methods added by https://api.rubyonrails.org/classes/ActiveModel/Dirty.html
93
+ # ## Also the methods added by https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/BeforeTypeCast.html
94
+ # end
95
+ # ~~~
96
+ class ActiveRecordColumns < Base
97
+ extend T::Sig
98
+
99
+ sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(ActiveRecord::Base)).void }
100
+ def decorate(root, constant)
101
+ return unless constant.table_exists?
102
+
103
+ module_name = "#{constant}::GeneratedAttributeMethods"
104
+ root.create_module(module_name) do |mod|
105
+ constant.columns_hash.each_key do |column_name|
106
+ column_name = column_name.to_s
107
+ add_methods_for_attribute(mod, constant, column_name)
108
+ end
109
+
110
+ constant.attribute_aliases.each do |attribute_name, column_name|
111
+ attribute_name = attribute_name.to_s
112
+ column_name = column_name.to_s
113
+ new_method_names = constant.attribute_method_matchers.map { |m| m.method_name(attribute_name) }
114
+ old_method_names = constant.attribute_method_matchers.map { |m| m.method_name(column_name) }
115
+ methods_to_add = new_method_names - old_method_names
116
+
117
+ add_methods_for_attribute(mod, constant, column_name, attribute_name, methods_to_add)
118
+ end
119
+ end
120
+
121
+ root.path(constant) do |klass|
122
+ klass.create_include(module_name)
123
+ end
124
+ end
125
+
126
+ sig { override.returns(T::Enumerable[Module]) }
127
+ def gather_constants
128
+ ActiveRecord::Base.descendants.reject(&:abstract_class?)
129
+ end
130
+
131
+ private
132
+
133
+ sig do
134
+ params(
135
+ klass: Parlour::RbiGenerator::Namespace,
136
+ name: String,
137
+ methods_to_add: T.nilable(T::Array[String]),
138
+ return_type: T.nilable(String),
139
+ parameters: T::Array[[String, String]]
140
+ ).void
141
+ end
142
+ def add_method(klass, name, methods_to_add, return_type: nil, parameters: [])
143
+ create_method(
144
+ klass,
145
+ name,
146
+ parameters: parameters.map do |param, type|
147
+ Parlour::RbiGenerator::Parameter.new(param, type: type)
148
+ end,
149
+ return_type: return_type
150
+ ) if methods_to_add.nil? || methods_to_add.include?(name)
151
+ end
152
+
153
+ sig do
154
+ params(
155
+ klass: Parlour::RbiGenerator::Namespace,
156
+ constant: T.class_of(ActiveRecord::Base),
157
+ column_name: String,
158
+ attribute_name: String,
159
+ methods_to_add: T.nilable(T::Array[String])
160
+ ).void
161
+ end
162
+ def add_methods_for_attribute(klass, constant, column_name, attribute_name = column_name, methods_to_add = nil)
163
+ getter_type, setter_type = type_for(constant, column_name)
164
+
165
+ # Added by ActiveRecord::AttributeMethods::Read
166
+ #
167
+ add_method(
168
+ klass,
169
+ attribute_name.to_s,
170
+ methods_to_add,
171
+ return_type: getter_type
172
+ )
173
+
174
+ # Added by ActiveRecord::AttributeMethods::Write
175
+ #
176
+ add_method(
177
+ klass,
178
+ "#{attribute_name}=",
179
+ methods_to_add,
180
+ parameters: [["value", setter_type]],
181
+ return_type: setter_type
182
+ )
183
+
184
+ # Added by ActiveRecord::AttributeMethods::Query
185
+ #
186
+ add_method(
187
+ klass,
188
+ "#{attribute_name}?",
189
+ methods_to_add,
190
+ return_type: "T::Boolean"
191
+ )
192
+
193
+ # Added by ActiveRecord::AttributeMethods::Dirty
194
+ #
195
+ add_method(
196
+ klass,
197
+ "#{attribute_name}_before_last_save",
198
+ methods_to_add,
199
+ return_type: getter_type
200
+ )
201
+ add_method(
202
+ klass,
203
+ "#{attribute_name}_change_to_be_saved",
204
+ methods_to_add,
205
+ return_type: "[#{getter_type}, #{getter_type}]"
206
+ )
207
+ add_method(
208
+ klass,
209
+ "#{attribute_name}_in_database",
210
+ methods_to_add,
211
+ return_type: getter_type
212
+ )
213
+ add_method(
214
+ klass,
215
+ "saved_change_to_#{attribute_name}",
216
+ methods_to_add,
217
+ return_type: "[#{getter_type}, #{getter_type}]"
218
+ )
219
+ add_method(
220
+ klass,
221
+ "saved_change_to_#{attribute_name}?",
222
+ methods_to_add,
223
+ return_type: "T::Boolean"
224
+ )
225
+ add_method(
226
+ klass,
227
+ "will_save_change_to_#{attribute_name}?",
228
+ methods_to_add,
229
+ return_type: "T::Boolean"
230
+ )
231
+
232
+ # Added by ActiveModel::Dirty
233
+ #
234
+ add_method(
235
+ klass,
236
+ "#{attribute_name}_change",
237
+ methods_to_add,
238
+ return_type: "[#{getter_type}, #{getter_type}]"
239
+ )
240
+ add_method(
241
+ klass,
242
+ "#{attribute_name}_changed?",
243
+ methods_to_add,
244
+ return_type: "T::Boolean"
245
+ )
246
+ add_method(
247
+ klass,
248
+ "#{attribute_name}_will_change!",
249
+ methods_to_add
250
+ )
251
+ add_method(
252
+ klass,
253
+ "#{attribute_name}_was",
254
+ methods_to_add,
255
+ return_type: getter_type
256
+ )
257
+ add_method(
258
+ klass,
259
+ "#{attribute_name}_previous_change",
260
+ methods_to_add,
261
+ return_type: "[#{getter_type}, #{getter_type}]"
262
+ )
263
+ add_method(
264
+ klass,
265
+ "#{attribute_name}_previously_changed?",
266
+ methods_to_add,
267
+ return_type: "T::Boolean"
268
+ )
269
+ add_method(
270
+ klass,
271
+ "#{attribute_name}_previously_was",
272
+ methods_to_add,
273
+ return_type: getter_type
274
+ )
275
+ add_method(
276
+ klass,
277
+ "restore_#{attribute_name}!",
278
+ methods_to_add
279
+ )
280
+
281
+ # Added by ActiveRecord::AttributeMethods::BeforeTypeCast
282
+ #
283
+ add_method(
284
+ klass,
285
+ "#{attribute_name}_before_type_cast",
286
+ methods_to_add,
287
+ return_type: "T.untyped"
288
+ )
289
+ add_method(
290
+ klass,
291
+ "#{attribute_name}_came_from_user?",
292
+ methods_to_add,
293
+ return_type: "T::Boolean"
294
+ )
295
+ end
296
+
297
+ sig do
298
+ params(
299
+ constant: T.class_of(ActiveRecord::Base),
300
+ column_name: String
301
+ ).returns([String, String])
302
+ end
303
+ def type_for(constant, column_name)
304
+ return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(constant)
305
+
306
+ column_type = constant.attribute_types[column_name]
307
+
308
+ getter_type =
309
+ case column_type
310
+ when ActiveRecord::Type::Integer
311
+ "::Integer"
312
+ when ActiveRecord::Type::String
313
+ "::String"
314
+ when ActiveRecord::Type::Date
315
+ "::Date"
316
+ when ActiveRecord::Type::Decimal
317
+ "::BigDecimal"
318
+ when ActiveRecord::Type::Float
319
+ "::Float"
320
+ when ActiveRecord::Type::Boolean
321
+ "T::Boolean"
322
+ when ActiveRecord::Type::DateTime, ActiveRecord::Type::Time
323
+ "::DateTime"
324
+ when ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter
325
+ "::ActiveSupport::TimeWithZone"
326
+ else
327
+ handle_unknown_type(column_type)
328
+ end
329
+
330
+ column = constant.columns_hash[column_name]
331
+ setter_type = getter_type
332
+
333
+ if column&.null
334
+ return ["T.nilable(#{getter_type})", "T.nilable(#{setter_type})"]
335
+ end
336
+
337
+ if column_name == constant.primary_key ||
338
+ column_name == "created_at" ||
339
+ column_name == "updated_at"
340
+ getter_type = "T.nilable(#{getter_type})"
341
+ end
342
+
343
+ [getter_type, setter_type]
344
+ end
345
+
346
+ sig { params(constant: Module).returns(T::Boolean) }
347
+ def do_not_generate_strong_types?(constant)
348
+ Object.const_defined?(:StrongTypeGeneration) &&
349
+ !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
350
+ end
351
+
352
+ sig { params(column_type: Module).returns(String) }
353
+ def handle_unknown_type(column_type)
354
+ return "T.untyped" unless column_type < ActiveModel::Type::Value
355
+
356
+ lookup_return_type_of_method(column_type, :deserialize) ||
357
+ lookup_return_type_of_method(column_type, :cast) ||
358
+ lookup_arg_type_of_method(column_type, :serialize) ||
359
+ "T.untyped"
360
+ end
361
+
362
+ sig { params(column_type: Module, method: Symbol).returns(T.nilable(String)) }
363
+ def lookup_return_type_of_method(column_type, method)
364
+ signature = T::Private::Methods.signature_for_method(column_type.instance_method(method))
365
+ return unless signature
366
+
367
+ return_type = signature.return_type.to_s
368
+ return_type if return_type != "<VOID>" && return_type != "<NOT-TYPED>"
369
+ end
370
+
371
+ sig { params(column_type: Module, method: Symbol).returns(T.nilable(String)) }
372
+ def lookup_arg_type_of_method(column_type, method)
373
+ signature = T::Private::Methods.signature_for_method(column_type.instance_method(method))
374
+ signature.arg_types.first.last.to_s if signature
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end