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.
- checksums.yaml +4 -4
- data/Gemfile +15 -15
- data/README.md +2 -2
- data/Rakefile +5 -7
- data/exe/tapioca +2 -2
- data/lib/tapioca/cli.rb +172 -2
- data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
- data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
- data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
- data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
- data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
- data/lib/tapioca/compilers/dsl/active_model_secure_password.rb +101 -0
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
- data/lib/tapioca/compilers/dsl/active_record_fixtures.rb +86 -0
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
- data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
- data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
- data/lib/tapioca/compilers/dsl/active_support_concern.rb +106 -0
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
- data/lib/tapioca/compilers/dsl/base.rb +108 -82
- data/lib/tapioca/compilers/dsl/config.rb +111 -0
- data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
- data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
- data/lib/tapioca/compilers/dsl/mixed_in_class_attributes.rb +74 -0
- data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
- data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
- data/lib/tapioca/compilers/dsl/smart_properties.rb +21 -33
- data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
- data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
- data/lib/tapioca/compilers/dsl_compiler.rb +25 -40
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +198 -0
- data/lib/tapioca/compilers/requires_compiler.rb +2 -2
- data/lib/tapioca/compilers/sorbet.rb +25 -5
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +122 -206
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
- data/lib/tapioca/compilers/symbol_table_compiler.rb +5 -11
- data/lib/tapioca/compilers/todos_compiler.rb +1 -1
- data/lib/tapioca/config.rb +3 -0
- data/lib/tapioca/config_builder.rb +5 -2
- data/lib/tapioca/constant_locator.rb +6 -8
- data/lib/tapioca/gemfile.rb +14 -11
- data/lib/tapioca/generators/base.rb +61 -0
- data/lib/tapioca/generators/dsl.rb +362 -0
- data/lib/tapioca/generators/gem.rb +345 -0
- data/lib/tapioca/generators/init.rb +79 -0
- data/lib/tapioca/generators/require.rb +52 -0
- data/lib/tapioca/generators/todo.rb +76 -0
- data/lib/tapioca/generators.rb +9 -0
- data/lib/tapioca/generic_type_registry.rb +25 -98
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
- data/lib/tapioca/internal.rb +2 -10
- data/lib/tapioca/loader.rb +11 -31
- data/lib/tapioca/rbi_ext/model.rb +166 -0
- data/lib/tapioca/reflection.rb +138 -0
- data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
- data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +3 -0
- metadata +45 -23
- data/lib/tapioca/cli/main.rb +0 -146
- data/lib/tapioca/core_ext/class.rb +0 -28
- data/lib/tapioca/core_ext/string.rb +0 -18
- data/lib/tapioca/generator.rb +0 -633
- data/lib/tapioca/rbi/model.rb +0 -405
- data/lib/tapioca/rbi/printer.rb +0 -410
- data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
- data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
- data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
- data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
- 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:
|
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.
|
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.
|
125
|
+
descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
|
128
126
|
end
|
129
127
|
|
130
128
|
private
|
131
129
|
|
132
|
-
sig { params(mod:
|
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:
|
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:
|
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
|
-
|
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
|
-
|
193
|
-
|
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
|
-
|
202
|
-
|
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
|
-
|
211
|
-
|
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:
|
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
|
-
|
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
|
-
|
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
|
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:
|
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.
|
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.
|
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:
|
137
|
+
klass: RBI::Scope,
|
140
138
|
name: String,
|
141
139
|
methods_to_add: T.nilable(T::Array[String]),
|
142
|
-
return_type:
|
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:
|
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
|
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:
|
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(
|
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:
|
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.
|
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(
|
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.
|
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:
|
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"
|
106
|
+
return_type = method.end_with?("?") ? "T::Boolean" : "void"
|
109
107
|
|
110
|
-
create_method(
|
108
|
+
klass.create_method(method, return_type: return_type)
|
111
109
|
end
|
112
110
|
end
|
113
111
|
end
|