tapioca 0.3.1 → 0.4.4
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 +25 -1
- data/README.md +23 -2
- data/Rakefile +15 -4
- data/lib/tapioca.rb +8 -2
- data/lib/tapioca/cli.rb +32 -3
- data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +129 -0
- data/lib/tapioca/compilers/dsl/action_mailer.rb +65 -0
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +267 -0
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +393 -0
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +112 -0
- data/lib/tapioca/compilers/dsl/active_record_identity_cache.rb +213 -0
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +100 -0
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +170 -0
- data/lib/tapioca/compilers/dsl/active_resource.rb +140 -0
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +126 -0
- data/lib/tapioca/compilers/dsl/base.rb +165 -0
- data/lib/tapioca/compilers/dsl/frozen_record.rb +96 -0
- data/lib/tapioca/compilers/dsl/protobuf.rb +144 -0
- data/lib/tapioca/compilers/dsl/smart_properties.rb +173 -0
- data/lib/tapioca/compilers/dsl/state_machines.rb +378 -0
- data/lib/tapioca/compilers/dsl/url_helpers.rb +92 -0
- data/lib/tapioca/compilers/dsl_compiler.rb +121 -0
- data/lib/tapioca/compilers/requires_compiler.rb +67 -0
- data/lib/tapioca/compilers/sorbet.rb +34 -0
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +171 -26
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +1 -20
- data/lib/tapioca/compilers/todos_compiler.rb +32 -0
- data/lib/tapioca/config.rb +14 -6
- data/lib/tapioca/config_builder.rb +22 -9
- data/lib/tapioca/constant_locator.rb +1 -0
- data/lib/tapioca/core_ext/class.rb +23 -0
- data/lib/tapioca/gemfile.rb +32 -9
- data/lib/tapioca/generator.rb +231 -23
- data/lib/tapioca/loader.rb +30 -9
- data/lib/tapioca/version.rb +1 -1
- metadata +32 -39
@@ -0,0 +1,140 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "parlour"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "active_resource"
|
8
|
+
rescue LoadError
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
module Tapioca
|
13
|
+
module Compilers
|
14
|
+
module Dsl
|
15
|
+
# `Tapioca::Compilers::Dsl::ActiveResource` decorates RBI files for subclasses of
|
16
|
+
# `ActiveResource::Base` which declare `schema` fields
|
17
|
+
# (see https://github.com/rails/activeresource).
|
18
|
+
#
|
19
|
+
# For example, with the following `ActiveResource::Base` subclass:
|
20
|
+
#
|
21
|
+
# ~~~rb
|
22
|
+
# class Post < ActiveResource::Base
|
23
|
+
# schema do
|
24
|
+
# integer 'id', 'month', 'year'
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# ~~~
|
28
|
+
#
|
29
|
+
# this generator will produce the RBI file `post.rbi` with the following content:
|
30
|
+
#
|
31
|
+
# ~~~rbi
|
32
|
+
# # post.rbi
|
33
|
+
# # typed: true
|
34
|
+
# class Post
|
35
|
+
# sig { returns(Integer) }
|
36
|
+
# def id; end
|
37
|
+
#
|
38
|
+
# sig { params(id: Integer).returns(Integer) }
|
39
|
+
# def id=(id); end
|
40
|
+
#
|
41
|
+
# sig { returns(T::Boolean) }
|
42
|
+
# def id?; end
|
43
|
+
#
|
44
|
+
# sig { returns(Integer) }
|
45
|
+
# def month; end
|
46
|
+
#
|
47
|
+
# sig { params(month: Integer).returns(Integer) }
|
48
|
+
# def month=(month); end
|
49
|
+
#
|
50
|
+
# sig { returns(T::Boolean) }
|
51
|
+
# def month?; end
|
52
|
+
#
|
53
|
+
# sig { returns(Integer) }
|
54
|
+
# def year; end
|
55
|
+
#
|
56
|
+
# sig { params(year: Integer).returns(Integer) }
|
57
|
+
# def year=(year); end
|
58
|
+
#
|
59
|
+
# sig { returns(T::Boolean) }
|
60
|
+
# def year?; end
|
61
|
+
# end
|
62
|
+
# ~~~
|
63
|
+
class ActiveResource < Base
|
64
|
+
extend T::Sig
|
65
|
+
|
66
|
+
sig do
|
67
|
+
override.params(
|
68
|
+
root: Parlour::RbiGenerator::Namespace,
|
69
|
+
constant: T.class_of(::ActiveResource::Base)
|
70
|
+
).void
|
71
|
+
end
|
72
|
+
def decorate(root, constant)
|
73
|
+
return if constant.schema.blank?
|
74
|
+
root.path(constant) do |klass|
|
75
|
+
constant.schema.each do |attribute, type|
|
76
|
+
create_schema_methods(klass, attribute, type)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
sig { override.returns(T::Enumerable[Module]) }
|
82
|
+
def gather_constants
|
83
|
+
::ActiveResource::Base.descendants
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
TYPES = T.let({
|
89
|
+
boolean: "T::Boolean",
|
90
|
+
integer: "Integer",
|
91
|
+
string: "String",
|
92
|
+
float: "Float",
|
93
|
+
date: "Date",
|
94
|
+
time: "Time",
|
95
|
+
datetime: "DateTime",
|
96
|
+
decimal: "BigDecimal",
|
97
|
+
binary: "String",
|
98
|
+
text: "String",
|
99
|
+
}.freeze, T::Hash[Symbol, String])
|
100
|
+
|
101
|
+
sig { params(attr_type: Symbol).returns(String) }
|
102
|
+
def type_for(attr_type)
|
103
|
+
TYPES.fetch(attr_type, "T.untyped")
|
104
|
+
end
|
105
|
+
|
106
|
+
sig do
|
107
|
+
params(
|
108
|
+
klass: Parlour::RbiGenerator::Namespace,
|
109
|
+
attribute: String,
|
110
|
+
type: String
|
111
|
+
).void
|
112
|
+
end
|
113
|
+
def create_schema_methods(klass, attribute, type)
|
114
|
+
return_type = type_for(type.to_sym)
|
115
|
+
|
116
|
+
create_method(
|
117
|
+
klass,
|
118
|
+
attribute,
|
119
|
+
return_type: return_type
|
120
|
+
)
|
121
|
+
|
122
|
+
create_method(
|
123
|
+
klass,
|
124
|
+
"#{attribute}?",
|
125
|
+
return_type: "T::Boolean"
|
126
|
+
)
|
127
|
+
|
128
|
+
create_method(
|
129
|
+
klass,
|
130
|
+
"#{attribute}=",
|
131
|
+
parameters: [
|
132
|
+
Parlour::RbiGenerator::Parameter.new("value", type: return_type),
|
133
|
+
],
|
134
|
+
return_type: return_type
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "parlour"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "active_support"
|
8
|
+
rescue LoadError
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
module Tapioca
|
13
|
+
module Compilers
|
14
|
+
module Dsl
|
15
|
+
# `Tapioca::Compilers::Dsl::ActiveSupportCurrentAttributes` decorates RBI files for all
|
16
|
+
# subclasses of `ActiveSupport::CurrentAttributes`
|
17
|
+
#
|
18
|
+
# To add attributes see https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html
|
19
|
+
#
|
20
|
+
# For example, with the following singleton class
|
21
|
+
#
|
22
|
+
# ~~~rb
|
23
|
+
# class Current < ActiveSupport::CurrentAttributes
|
24
|
+
# extend T::Sig
|
25
|
+
#
|
26
|
+
# attribute :account
|
27
|
+
#
|
28
|
+
# def helper
|
29
|
+
# # ...
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# sig { params(user_id: Integer).void }
|
33
|
+
# def authenticate(user_id)
|
34
|
+
# # ...
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# ~~~rb
|
38
|
+
#
|
39
|
+
# this generator will produce an RBI file with the following content:
|
40
|
+
# ~~~rbi
|
41
|
+
# # typed: true
|
42
|
+
#
|
43
|
+
# class Current
|
44
|
+
# sig { returns(T.untyped) }
|
45
|
+
# def self.account; end
|
46
|
+
#
|
47
|
+
# sig { returns(T.untyped) }
|
48
|
+
# def account; end
|
49
|
+
#
|
50
|
+
# sig { params(account: T.untyped).returns(T.untyped) }
|
51
|
+
# def self.account=(account); end
|
52
|
+
#
|
53
|
+
# sig { params(account: T.untyped).returns(T.untyped) }
|
54
|
+
# def account=(account); end
|
55
|
+
#
|
56
|
+
# sig { params(user_id: Integer).void }
|
57
|
+
# def self.authenticate(user_id); end
|
58
|
+
#
|
59
|
+
# sig { returns(T.untyped) }
|
60
|
+
# def self.helper; end
|
61
|
+
# end
|
62
|
+
# ~~~
|
63
|
+
class ActiveSupportCurrentAttributes < Base
|
64
|
+
extend T::Sig
|
65
|
+
|
66
|
+
sig do
|
67
|
+
override
|
68
|
+
.params(
|
69
|
+
root: Parlour::RbiGenerator::Namespace,
|
70
|
+
constant: T.class_of(::ActiveSupport::CurrentAttributes)
|
71
|
+
)
|
72
|
+
.void
|
73
|
+
end
|
74
|
+
def decorate(root, constant)
|
75
|
+
dynamic_methods = dynamic_methods_for(constant)
|
76
|
+
instance_methods = instance_methods_for(constant) - dynamic_methods
|
77
|
+
return if dynamic_methods.empty? && instance_methods.empty?
|
78
|
+
|
79
|
+
root.path(constant) do |k|
|
80
|
+
dynamic_methods.each do |method|
|
81
|
+
method = method.to_s
|
82
|
+
# We want to generate each method both on the class
|
83
|
+
generate_method(k, method, class_method: true)
|
84
|
+
# and on the instance
|
85
|
+
generate_method(k, method, class_method: false)
|
86
|
+
end
|
87
|
+
|
88
|
+
instance_methods.each do |method|
|
89
|
+
# instance methods are only elevated to class methods
|
90
|
+
# no need to add separate instance methods for them
|
91
|
+
method = constant.instance_method(method)
|
92
|
+
create_method_from_def(k, method, class_method: true)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { override.returns(T::Enumerable[Module]) }
|
98
|
+
def gather_constants
|
99
|
+
::ActiveSupport::CurrentAttributes.descendants
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
sig { params(constant: T.class_of(::ActiveSupport::CurrentAttributes)).returns(T::Array[Symbol]) }
|
105
|
+
def dynamic_methods_for(constant)
|
106
|
+
constant.instance_variable_get(:@generated_attribute_methods)&.instance_methods(false) || []
|
107
|
+
end
|
108
|
+
|
109
|
+
sig { params(constant: T.class_of(::ActiveSupport::CurrentAttributes)).returns(T::Array[Symbol]) }
|
110
|
+
def instance_methods_for(constant)
|
111
|
+
constant.instance_methods(false)
|
112
|
+
end
|
113
|
+
|
114
|
+
sig { params(klass: Parlour::RbiGenerator::Namespace, method: String, class_method: T::Boolean).void }
|
115
|
+
def generate_method(klass, method, class_method:)
|
116
|
+
if method.end_with?("=")
|
117
|
+
parameter = Parlour::RbiGenerator::Parameter.new("value", type: "T.untyped")
|
118
|
+
klass.create_method(method, class_method: class_method, parameters: [parameter], return_type: "T.untyped")
|
119
|
+
else
|
120
|
+
klass.create_method(method, class_method: class_method, return_type: "T.untyped")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "parlour"
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module Compilers
|
8
|
+
module Dsl
|
9
|
+
class Base
|
10
|
+
extend T::Sig
|
11
|
+
extend T::Helpers
|
12
|
+
|
13
|
+
abstract!
|
14
|
+
|
15
|
+
sig { returns(T::Set[Module]) }
|
16
|
+
attr_reader :processable_constants
|
17
|
+
|
18
|
+
sig { void }
|
19
|
+
def initialize
|
20
|
+
@processable_constants = T.let(Set.new(gather_constants), T::Set[Module])
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { params(constant: Module).returns(T::Boolean) }
|
24
|
+
def handles?(constant)
|
25
|
+
processable_constants.include?(constant)
|
26
|
+
end
|
27
|
+
|
28
|
+
sig do
|
29
|
+
abstract
|
30
|
+
.type_parameters(:T)
|
31
|
+
.params(
|
32
|
+
root: Parlour::RbiGenerator::Namespace,
|
33
|
+
constant: T.type_parameter(:T)
|
34
|
+
)
|
35
|
+
.void
|
36
|
+
end
|
37
|
+
def decorate(root, constant); end
|
38
|
+
|
39
|
+
sig { abstract.returns(T::Enumerable[Module]) }
|
40
|
+
def gather_constants; end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
SPECIAL_METHOD_NAMES = T.let(
|
45
|
+
%w[! ~ +@ ** -@ * / % + - << >> & | ^ < <= => > >= == === != =~ !~ <=> [] []= `].freeze,
|
46
|
+
T::Array[String]
|
47
|
+
)
|
48
|
+
|
49
|
+
sig { params(name: String).returns(T::Boolean) }
|
50
|
+
def valid_method_name?(name)
|
51
|
+
return true if SPECIAL_METHOD_NAMES.include?(name)
|
52
|
+
!!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
|
53
|
+
end
|
54
|
+
|
55
|
+
sig do
|
56
|
+
params(
|
57
|
+
namespace: Parlour::RbiGenerator::Namespace,
|
58
|
+
name: String,
|
59
|
+
options: T::Hash[T.untyped, T.untyped]
|
60
|
+
).void
|
61
|
+
end
|
62
|
+
def create_method(namespace, name, options = {})
|
63
|
+
return unless valid_method_name?(name)
|
64
|
+
T.unsafe(namespace).create_method(name, options)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Create a Parlour method inside `namespace` from its Ruby definition
|
68
|
+
sig do
|
69
|
+
params(
|
70
|
+
namespace: Parlour::RbiGenerator::Namespace,
|
71
|
+
method_def: T.any(Method, UnboundMethod),
|
72
|
+
class_method: T::Boolean
|
73
|
+
).void
|
74
|
+
end
|
75
|
+
def create_method_from_def(namespace, method_def, class_method: false)
|
76
|
+
create_method(
|
77
|
+
namespace,
|
78
|
+
method_def.name.to_s,
|
79
|
+
parameters: compile_method_parameters_to_parlour(method_def),
|
80
|
+
return_type: compile_method_return_type_to_parlour(method_def),
|
81
|
+
class_method: class_method
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Compile a Ruby method parameters into Parlour parameters
|
86
|
+
sig do
|
87
|
+
params(method_def: T.any(Method, UnboundMethod))
|
88
|
+
.returns(T::Array[Parlour::RbiGenerator::Parameter])
|
89
|
+
end
|
90
|
+
def compile_method_parameters_to_parlour(method_def)
|
91
|
+
signature = T::Private::Methods.signature_for_method(method_def)
|
92
|
+
method_def = signature.nil? ? method_def : signature.method
|
93
|
+
method_types = parameters_types_from_signature(method_def, signature)
|
94
|
+
|
95
|
+
method_def.parameters.each_with_index.map do |(type, name), i|
|
96
|
+
name ||= :_
|
97
|
+
name = name.to_s.gsub(/&|\*/, '_') # avoid incorrect names from `delegate`
|
98
|
+
case type
|
99
|
+
when :req
|
100
|
+
::Parlour::RbiGenerator::Parameter.new(name, type: method_types[i])
|
101
|
+
when :opt
|
102
|
+
::Parlour::RbiGenerator::Parameter.new(name, type: method_types[i], default: 'T.unsafe(nil)')
|
103
|
+
when :rest
|
104
|
+
::Parlour::RbiGenerator::Parameter.new("*#{name}", type: method_types[i])
|
105
|
+
when :keyreq
|
106
|
+
::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_types[i])
|
107
|
+
when :key
|
108
|
+
::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_types[i], default: 'T.unsafe(nil)')
|
109
|
+
when :keyrest
|
110
|
+
::Parlour::RbiGenerator::Parameter.new("**#{name}", type: method_types[i])
|
111
|
+
when :block
|
112
|
+
::Parlour::RbiGenerator::Parameter.new("&#{name}", type: method_types[i])
|
113
|
+
else
|
114
|
+
raise "Unknown type `#{type}`."
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Compile a Ruby method return type into a Parlour type
|
120
|
+
sig do
|
121
|
+
params(method_def: T.any(Method, UnboundMethod))
|
122
|
+
.returns(String)
|
123
|
+
end
|
124
|
+
def compile_method_return_type_to_parlour(method_def)
|
125
|
+
signature = T::Private::Methods.signature_for_method(method_def)
|
126
|
+
return_type = signature.nil? ? 'T.untyped' : signature.return_type.to_s
|
127
|
+
# Map <VOID> to `nil` since `nil` means a `void` return for Parlour
|
128
|
+
return_type = nil if return_type == "<VOID>"
|
129
|
+
# Map <NOT-TYPED> to `T.untyped`
|
130
|
+
return_type = "T.untyped" if return_type == "<NOT-TYPED>"
|
131
|
+
return_type
|
132
|
+
end
|
133
|
+
|
134
|
+
# Get the types of each parameter from a method signature
|
135
|
+
sig do
|
136
|
+
params(
|
137
|
+
method_def: T.any(Method, UnboundMethod),
|
138
|
+
signature: T.untyped # as `T::Private::Methods::Signature` is private
|
139
|
+
).returns(T::Array[String])
|
140
|
+
end
|
141
|
+
def parameters_types_from_signature(method_def, signature)
|
142
|
+
params = T.let([], T::Array[String])
|
143
|
+
|
144
|
+
return method_def.parameters.map { 'T.untyped' } unless signature
|
145
|
+
|
146
|
+
# parameters types
|
147
|
+
signature.arg_types.each { |arg_type| params << arg_type[1].to_s }
|
148
|
+
|
149
|
+
# keyword parameters types
|
150
|
+
signature.kwarg_types.each { |_, kwarg_type| params << kwarg_type.to_s }
|
151
|
+
|
152
|
+
# rest parameter type
|
153
|
+
params << signature.rest_type.to_s if signature.has_rest
|
154
|
+
|
155
|
+
# special case `.void` in a proc
|
156
|
+
unless signature.block_name.nil?
|
157
|
+
params << signature.block_type.to_s.gsub('returns(<VOID>)', 'void')
|
158
|
+
end
|
159
|
+
|
160
|
+
params
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "parlour"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "frozen_record"
|
8
|
+
rescue LoadError
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
module Tapioca
|
13
|
+
module Compilers
|
14
|
+
module Dsl
|
15
|
+
# `Tapioca::Compilers::Dsl::FrozenRecord` generates RBI files for subclasses of `FrozenRecord::Base`
|
16
|
+
# (see https://github.com/byroot/frozen_record).
|
17
|
+
#
|
18
|
+
# For example, with the following FrozenRecord class:
|
19
|
+
#
|
20
|
+
# ~~~rb
|
21
|
+
# # student.rb
|
22
|
+
# class Student < FrozenRecord::Base
|
23
|
+
# end
|
24
|
+
# ~~~
|
25
|
+
#
|
26
|
+
# and the following YAML file:
|
27
|
+
#
|
28
|
+
# ~~~ yaml
|
29
|
+
# # students.yml
|
30
|
+
# - id: 1
|
31
|
+
# first_name: John
|
32
|
+
# last_name: Smith
|
33
|
+
# - id: 2
|
34
|
+
# first_name: Dan
|
35
|
+
# last_name: Lord
|
36
|
+
# ~~~
|
37
|
+
#
|
38
|
+
# this generator will produce the RBI file `student.rbi` with the following content:
|
39
|
+
#
|
40
|
+
# ~~~rbi
|
41
|
+
# # Student.rbi
|
42
|
+
# # typed: strong
|
43
|
+
# class Student
|
44
|
+
# include Student::FrozenRecordAttributeMethods
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# module Student::FrozenRecordAttributeMethods
|
48
|
+
# sig { returns(T.untyped) }
|
49
|
+
# def first_name; end
|
50
|
+
#
|
51
|
+
# sig { returns(T::Boolean) }
|
52
|
+
# def first_name?; end
|
53
|
+
#
|
54
|
+
# sig { returns(T.untyped) }
|
55
|
+
# def id; end
|
56
|
+
#
|
57
|
+
# sig { returns(T::Boolean) }
|
58
|
+
# def id?; end
|
59
|
+
#
|
60
|
+
# sig { returns(T.untyped) }
|
61
|
+
# def last_name; end
|
62
|
+
#
|
63
|
+
# sig { returns(T::Boolean) }
|
64
|
+
# def last_name?; end
|
65
|
+
# end
|
66
|
+
# ~~~
|
67
|
+
class FrozenRecord < Base
|
68
|
+
extend T::Sig
|
69
|
+
|
70
|
+
sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::FrozenRecord::Base)).void }
|
71
|
+
def decorate(root, constant)
|
72
|
+
attributes = constant.attributes
|
73
|
+
return if attributes.empty?
|
74
|
+
|
75
|
+
module_name = "#{constant}::FrozenRecordAttributeMethods"
|
76
|
+
|
77
|
+
root.create_module(module_name) do |mod|
|
78
|
+
attributes.each do |attribute|
|
79
|
+
create_method(mod, "#{attribute}?", return_type: 'T::Boolean')
|
80
|
+
create_method(mod, attribute.to_s, return_type: 'T.untyped')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
root.path(constant) do |klass|
|
85
|
+
klass.create_include(module_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { override.returns(T::Enumerable[Module]) }
|
90
|
+
def gather_constants
|
91
|
+
::FrozenRecord::Base.descendants.reject(&:abstract_class?)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|