tapioca 0.2.8 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +25 -1
- data/README.md +23 -2
- data/Rakefile +15 -4
- data/lib/tapioca.rb +15 -9
- data/lib/tapioca/cli.rb +41 -12
- 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 +285 -0
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +387 -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 +83 -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 +210 -50
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +3 -17
- data/lib/tapioca/compilers/todos_compiler.rb +32 -0
- data/lib/tapioca/config.rb +42 -0
- data/lib/tapioca/config_builder.rb +75 -0
- data/lib/tapioca/constant_locator.rb +1 -0
- data/lib/tapioca/core_ext/class.rb +23 -0
- data/lib/tapioca/gemfile.rb +44 -9
- data/lib/tapioca/generator.rb +248 -70
- data/lib/tapioca/loader.rb +20 -9
- data/lib/tapioca/sorbet_config_parser.rb +77 -0
- data/lib/tapioca/version.rb +1 -1
- metadata +33 -51
@@ -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
|