tapioca 0.2.7 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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