tapioca 0.4.24 → 0.5.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.
- checksums.yaml +4 -4
- data/Gemfile +14 -14
- data/README.md +2 -2
- data/Rakefile +5 -7
- data/exe/tapioca +2 -2
- data/lib/tapioca/cli.rb +256 -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_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_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 +108 -0
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
- data/lib/tapioca/compilers/dsl/base.rb +96 -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/protobuf.rb +19 -69
- data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
- data/lib/tapioca/compilers/dsl/smart_properties.rb +19 -31
- 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 +22 -38
- data/lib/tapioca/compilers/requires_compiler.rb +2 -2
- data/lib/tapioca/compilers/sorbet.rb +26 -5
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +139 -154
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
- data/lib/tapioca/compilers/symbol_table_compiler.rb +1 -1
- data/lib/tapioca/compilers/todos_compiler.rb +1 -1
- data/lib/tapioca/config.rb +2 -0
- data/lib/tapioca/config_builder.rb +4 -2
- data/lib/tapioca/constant_locator.rb +6 -8
- data/lib/tapioca/gemfile.rb +26 -19
- data/lib/tapioca/generator.rb +127 -43
- 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 +1 -9
- data/lib/tapioca/loader.rb +14 -48
- data/lib/tapioca/rbi_ext/model.rb +122 -0
- data/lib/tapioca/reflection.rb +131 -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 +2 -0
- metadata +34 -22
- 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/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
@@ -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
|
@@ -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
|
@@ -47,7 +45,7 @@ module Tapioca
|
|
47
45
|
|
48
46
|
sig do
|
49
47
|
override.params(
|
50
|
-
root:
|
48
|
+
root: RBI::Tree,
|
51
49
|
constant: T.class_of(::ActiveRecord::Base)
|
52
50
|
).void
|
53
51
|
end
|
@@ -55,7 +53,7 @@ module Tapioca
|
|
55
53
|
scope_method_names = constant.send(:generated_relation_methods).instance_methods(false)
|
56
54
|
return if scope_method_names.empty?
|
57
55
|
|
58
|
-
root.
|
56
|
+
root.create_path(constant) do |model|
|
59
57
|
module_name = "GeneratedRelationMethods"
|
60
58
|
|
61
59
|
model.create_module(module_name) do |mod|
|
@@ -70,7 +68,7 @@ module Tapioca
|
|
70
68
|
|
71
69
|
sig { override.returns(T::Enumerable[Module]) }
|
72
70
|
def gather_constants
|
73
|
-
::ActiveRecord::Base.
|
71
|
+
descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
|
74
72
|
end
|
75
73
|
|
76
74
|
private
|
@@ -78,19 +76,18 @@ module Tapioca
|
|
78
76
|
sig do
|
79
77
|
params(
|
80
78
|
scope_method: String,
|
81
|
-
mod:
|
79
|
+
mod: RBI::Scope,
|
82
80
|
).void
|
83
81
|
end
|
84
82
|
def generate_scope_method(scope_method, mod)
|
85
83
|
# This return type should actually be Model::ActiveRecord_Relation
|
86
84
|
return_type = "T.untyped"
|
87
85
|
|
88
|
-
create_method(
|
89
|
-
mod,
|
86
|
+
mod.create_method(
|
90
87
|
scope_method,
|
91
88
|
parameters: [
|
92
|
-
|
93
|
-
|
89
|
+
create_rest_param("args", type: "T.untyped"),
|
90
|
+
create_block_param("blk", type: "T.untyped"),
|
94
91
|
],
|
95
92
|
return_type: return_type,
|
96
93
|
)
|
@@ -1,9 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "parlour"
|
5
|
-
require "tapioca/core_ext/class"
|
6
|
-
|
7
4
|
begin
|
8
5
|
require "activerecord-typedstore"
|
9
6
|
rescue LoadError
|
@@ -92,7 +89,7 @@ module Tapioca
|
|
92
89
|
sig do
|
93
90
|
override
|
94
91
|
.params(
|
95
|
-
root:
|
92
|
+
root: RBI::Tree,
|
96
93
|
constant: T.class_of(::ActiveRecord::Base)
|
97
94
|
)
|
98
95
|
.void
|
@@ -101,7 +98,7 @@ module Tapioca
|
|
101
98
|
stores = constant.typed_stores
|
102
99
|
return if stores.values.flat_map(&:accessors).empty?
|
103
100
|
|
104
|
-
root.
|
101
|
+
root.create_path(constant) do |model|
|
105
102
|
stores.values.each do |store_data|
|
106
103
|
store_data.accessors.each do |accessor|
|
107
104
|
field = store_data.fields[accessor]
|
@@ -116,7 +113,7 @@ module Tapioca
|
|
116
113
|
|
117
114
|
sig { override.returns(T::Enumerable[Module]) }
|
118
115
|
def gather_constants
|
119
|
-
::ActiveRecord::Base.
|
116
|
+
descendants_of(::ActiveRecord::Base).select do |klass|
|
120
117
|
klass.include?(ActiveRecord::TypedStore::Behavior)
|
121
118
|
end
|
122
119
|
end
|
@@ -142,7 +139,7 @@ module Tapioca
|
|
142
139
|
|
143
140
|
sig do
|
144
141
|
params(
|
145
|
-
klass:
|
142
|
+
klass: RBI::Scope,
|
146
143
|
name: String,
|
147
144
|
type: String
|
148
145
|
)
|
@@ -151,7 +148,7 @@ module Tapioca
|
|
151
148
|
def generate_methods(klass, name, type)
|
152
149
|
klass.create_method(
|
153
150
|
"#{name}=",
|
154
|
-
parameters: [
|
151
|
+
parameters: [create_param(name, type: type)],
|
155
152
|
return_type: type
|
156
153
|
)
|
157
154
|
klass.create_method(name, return_type: type)
|
@@ -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_resource"
|
8
6
|
rescue LoadError
|
@@ -63,16 +61,11 @@ module Tapioca
|
|
63
61
|
class ActiveResource < Base
|
64
62
|
extend T::Sig
|
65
63
|
|
66
|
-
sig
|
67
|
-
override.params(
|
68
|
-
root: Parlour::RbiGenerator::Namespace,
|
69
|
-
constant: T.class_of(::ActiveResource::Base)
|
70
|
-
).void
|
71
|
-
end
|
64
|
+
sig { override.params(root: RBI::Tree, constant: T.class_of(::ActiveResource::Base)).void }
|
72
65
|
def decorate(root, constant)
|
73
66
|
return if constant.schema.blank?
|
74
67
|
|
75
|
-
root.
|
68
|
+
root.create_path(constant) do |resource|
|
76
69
|
constant.schema.each do |attribute, type|
|
77
70
|
create_schema_methods(resource, attribute, type)
|
78
71
|
end
|
@@ -81,7 +74,7 @@ module Tapioca
|
|
81
74
|
|
82
75
|
sig { override.returns(T::Enumerable[Module]) }
|
83
76
|
def gather_constants
|
84
|
-
::ActiveResource::Base
|
77
|
+
descendants_of(::ActiveResource::Base)
|
85
78
|
end
|
86
79
|
|
87
80
|
private
|
@@ -104,36 +97,15 @@ module Tapioca
|
|
104
97
|
TYPES.fetch(attr_type, "T.untyped")
|
105
98
|
end
|
106
99
|
|
107
|
-
sig
|
108
|
-
params(
|
109
|
-
klass: Parlour::RbiGenerator::Namespace,
|
110
|
-
attribute: String,
|
111
|
-
type: String
|
112
|
-
).void
|
113
|
-
end
|
100
|
+
sig { params(klass: RBI::Scope, attribute: String, type: String).void }
|
114
101
|
def create_schema_methods(klass, attribute, type)
|
115
102
|
return_type = type_for(type.to_sym)
|
116
103
|
|
117
|
-
create_method(
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
)
|
122
|
-
|
123
|
-
create_method(
|
124
|
-
klass,
|
125
|
-
"#{attribute}?",
|
126
|
-
return_type: "T::Boolean"
|
127
|
-
)
|
128
|
-
|
129
|
-
create_method(
|
130
|
-
klass,
|
131
|
-
"#{attribute}=",
|
132
|
-
parameters: [
|
133
|
-
Parlour::RbiGenerator::Parameter.new("value", type: return_type),
|
134
|
-
],
|
135
|
-
return_type: return_type
|
136
|
-
)
|
104
|
+
klass.create_method(attribute, return_type: return_type)
|
105
|
+
klass.create_method("#{attribute}?", return_type: "T::Boolean")
|
106
|
+
klass.create_method("#{attribute}=", parameters: [
|
107
|
+
create_param("value", type: return_type),
|
108
|
+
], return_type: return_type)
|
137
109
|
end
|
138
110
|
end
|
139
111
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "active_storage"
|
6
|
+
require "active_storage/reflection"
|
7
|
+
rescue LoadError
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
11
|
+
module Tapioca
|
12
|
+
module Compilers
|
13
|
+
module Dsl
|
14
|
+
# `Tapioca::Compilers::Dsl::ActiveStorage` decorates RBI files for subclasses of
|
15
|
+
# `ActiveRecord::Base` that declare [one](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-one-attached)
|
16
|
+
# or [many](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-many-attached) attachments.
|
17
|
+
#
|
18
|
+
# For example, with the following `ActiveRecord::Base` subclass:
|
19
|
+
#
|
20
|
+
# ~~~rb
|
21
|
+
# class Post < ApplicationRecord
|
22
|
+
# has_one_attached :photo
|
23
|
+
# has_many_attached :blogs
|
24
|
+
# end
|
25
|
+
# ~~~
|
26
|
+
#
|
27
|
+
# this generator will produce the RBI file `post.rbi` with the following content:
|
28
|
+
#
|
29
|
+
# ~~~rbi
|
30
|
+
# # typed: strong
|
31
|
+
#
|
32
|
+
# class Post
|
33
|
+
# sig { returns(ActiveStorage::Attached::Many) }
|
34
|
+
# def blogs; end
|
35
|
+
#
|
36
|
+
# sig { params(attachable: T.untyped).returns(T.untyped) }
|
37
|
+
# def blogs=(attachable); end
|
38
|
+
#
|
39
|
+
# sig { returns(ActiveStorage::Attached::One) }
|
40
|
+
# def photo; end
|
41
|
+
#
|
42
|
+
# sig { params(attachable: T.untyped).returns(T.untyped) }
|
43
|
+
# def photo=(attachable); end
|
44
|
+
# end
|
45
|
+
# ~~~
|
46
|
+
class ActiveStorage < Base
|
47
|
+
extend T::Sig
|
48
|
+
|
49
|
+
sig do
|
50
|
+
override.params(root: RBI::Tree,
|
51
|
+
constant: T.all(Module, ::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods)).void
|
52
|
+
end
|
53
|
+
def decorate(root, constant)
|
54
|
+
return if constant.reflect_on_all_attachments.empty?
|
55
|
+
|
56
|
+
root.create_path(constant) do |scope|
|
57
|
+
constant.reflect_on_all_attachments.each do |reflection|
|
58
|
+
type = type_of(reflection)
|
59
|
+
name = reflection.name.to_s
|
60
|
+
scope.create_method(
|
61
|
+
name,
|
62
|
+
return_type: type
|
63
|
+
)
|
64
|
+
scope.create_method(
|
65
|
+
"#{name}=",
|
66
|
+
parameters: [create_param("attachable", type: "T.untyped")],
|
67
|
+
return_type: "T.untyped"
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
sig { override.returns(T::Enumerable[Module]) }
|
74
|
+
def gather_constants
|
75
|
+
descendants_of(::ActiveRecord::Base)
|
76
|
+
.reject(&:abstract_class?)
|
77
|
+
.grep(::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
sig do
|
83
|
+
params(reflection: ActiveRecord::Reflection::MacroReflection).returns(String)
|
84
|
+
end
|
85
|
+
def type_of(reflection)
|
86
|
+
case reflection
|
87
|
+
when ::ActiveStorage::Reflection::HasOneAttachedReflection
|
88
|
+
"ActiveStorage::Attached::One"
|
89
|
+
when ::ActiveStorage::Reflection::HasManyAttachedReflection
|
90
|
+
"ActiveStorage::Attached::Many"
|
91
|
+
else
|
92
|
+
"T.untyped"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|