rbs_rails 0.2.0 → 0.6.0
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/.gitignore +3 -0
- data/.gitmodules +3 -0
- data/.travis.yml +3 -0
- data/Gemfile +3 -2
- data/README.md +33 -35
- data/Rakefile +14 -1
- data/Steepfile +12 -1
- data/assets/sig/action_mailer.rbs +6 -3
- data/assets/sig/capybara.rbs +14 -0
- data/assets/sig/concurrent.rbs +4 -0
- data/assets/sig/erb.rbs +4 -0
- data/assets/sig/erubi.rbs +4 -0
- data/assets/sig/i18n.rbs +4 -0
- data/assets/sig/minitest.rbs +12 -1
- data/assets/sig/pg.rbs +5 -0
- data/assets/sig/que.rbs +4 -0
- data/assets/sig/queue_classic.rbs +4 -0
- data/assets/sig/racc.rbs +4 -0
- data/assets/sig/rack-test.rbs +6 -0
- data/assets/sig/rack.rbs +47 -0
- data/assets/sig/rails.rbs +7 -8
- data/assets/sig/rdoc.rbs +9 -0
- data/assets/sig/sidekiq.rbs +4 -0
- data/assets/sig/sneakers.rbs +4 -0
- data/assets/sig/stdlib.rbs +15 -5
- data/assets/sig/sucker_punch.rbs +4 -0
- data/assets/sig/thor.rbs +12 -0
- data/assets/sig/tzinfo.rbs +4 -0
- data/bin/add-type-params.rb +39 -13
- data/bin/postprocess.rb +137 -0
- data/bin/rbs +30 -0
- data/bin/rbs-prototype-rb.rb +195 -0
- data/bin/to-ascii.rb +5 -0
- data/lib/rbs_rails/active_record.rb +78 -33
- data/lib/rbs_rails/rake_task.rb +75 -0
- data/lib/rbs_rails/version.rb +1 -1
- data/rbs_rails.gemspec +1 -0
- data/sig/fileutils.rbs +1 -0
- data/sig/rake.rbs +6 -0
- data/sig/rbs_rails/active_record.rbs +4 -4
- data/sig/rbs_rails/rake_task.rbs +20 -0
- metadata +45 -12
- data/assets/sig/action_controller.rbs +0 -44
- data/assets/sig/action_view.rbs +0 -3
- data/assets/sig/active_record.rbs +0 -130
- data/assets/sig/generated/activemodel.rbs +0 -3877
- data/assets/sig/generated/activesupport.rbs +0 -11480
- data/bin/merge-duplicate-decls.rb +0 -30
data/bin/postprocess.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#!ruby
|
2
|
+
|
3
|
+
# TODO: Expose me to user
|
4
|
+
|
5
|
+
require 'bundler/inline'
|
6
|
+
|
7
|
+
gemfile do
|
8
|
+
source 'https://rubygems.org'
|
9
|
+
gem 'rbs', '1.0.0'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rbs'
|
13
|
+
require 'rbs/cli'
|
14
|
+
require 'optparse'
|
15
|
+
|
16
|
+
def env(options:)
|
17
|
+
loader = options.loader
|
18
|
+
RBS::Environment.from_loader(loader).resolve_type_names
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_option(argv)
|
22
|
+
opt = OptionParser.new
|
23
|
+
options = RBS::CLI::LibraryOptions.new
|
24
|
+
options.setup_library_options(opt)
|
25
|
+
|
26
|
+
return opt.parse(argv), options
|
27
|
+
end
|
28
|
+
|
29
|
+
class FileMatcher
|
30
|
+
def initialize(targets:)
|
31
|
+
base_dir = Dir.pwd
|
32
|
+
@targets = targets + targets.map { |t| File.expand_path(t, base_dir) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def match?(fname)
|
36
|
+
@targets.any? { |t| fname.start_with?(t) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def class_method_name(concern)
|
41
|
+
RBS::TypeName.new(namespace: concern.name.to_namespace, name: :ClassMethods)
|
42
|
+
end
|
43
|
+
|
44
|
+
def process(decl, env:, builder:, update_targets:)
|
45
|
+
concerns = decl.members.select do |m|
|
46
|
+
next false unless m.is_a?(RBS::AST::Members::Include)
|
47
|
+
next false unless m.name.kind == :class
|
48
|
+
|
49
|
+
mod_entry = env.class_decls[m.name]
|
50
|
+
unless mod_entry
|
51
|
+
warn "unknown type: #{m.name}"
|
52
|
+
next false
|
53
|
+
end
|
54
|
+
|
55
|
+
a = builder.singleton_ancestors(m.name)
|
56
|
+
a.ancestors.any? { |ancestor| ancestor.name.to_s == '::ActiveSupport::Concern' }
|
57
|
+
end
|
58
|
+
|
59
|
+
concerns.each do |concern|
|
60
|
+
class_methods_name = class_method_name(concern)
|
61
|
+
class_methods_type = env.class_decls[class_methods_name]
|
62
|
+
next unless class_methods_type
|
63
|
+
|
64
|
+
# Skip if the decl already extend ClassMethods
|
65
|
+
a = builder.singleton_ancestors(decl.name)
|
66
|
+
next if a.ancestors.any? { |ancestor| ancestor.name == class_methods_name }
|
67
|
+
|
68
|
+
# TODO: Insert `extend class_methods_name` to decl
|
69
|
+
update_targets << [decl, concern]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def each_decl_descendant(decl:, path: [], &block)
|
74
|
+
return unless decl.is_a?(RBS::AST::Declarations::Class) || decl.is_a?(RBS::AST::Declarations::Module)
|
75
|
+
|
76
|
+
block.call(decl: decl, path: path)
|
77
|
+
path = [*path, decl]
|
78
|
+
decl.each_decl do |child|
|
79
|
+
each_decl_descendant(decl: child, path: path, &block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def may_eql_member?(a, b)
|
84
|
+
a.name.to_s.split('::').last == b.name.to_s.split('::').last
|
85
|
+
end
|
86
|
+
|
87
|
+
def update!(update_targets:, only:)
|
88
|
+
update_targets.group_by { |decl, _concern| decl.location.name }.each do |fname, target_decls|
|
89
|
+
next unless only.match?(fname)
|
90
|
+
|
91
|
+
tree = RBS::Parser.parse_signature(File.read(fname))
|
92
|
+
target_decls.each do |target_decl, concern|
|
93
|
+
catch(:break) do
|
94
|
+
tree.each do |node|
|
95
|
+
each_decl_descendant(decl: node) do |decl:, path:|
|
96
|
+
next unless [relative = [*path, decl].map { |p| p.name.to_s }.join('::'), '::' + relative].include?(target_decl.name.to_s)
|
97
|
+
|
98
|
+
idx = decl.members.index { |m| may_eql_member?(m, concern) } || -1
|
99
|
+
extend = RBS::AST::Members::Extend.new(name: class_method_name(concern), args: [], annotations: [], location: nil, comment: nil)
|
100
|
+
decl.members.insert(idx + 1, extend)
|
101
|
+
throw :break
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
File.open(fname, 'w') do |f|
|
108
|
+
RBS::Writer.new(out: f).write(tree)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def run(argv)
|
114
|
+
targets, options = parse_option(argv)
|
115
|
+
env = env(options: options)
|
116
|
+
builder = RBS::DefinitionBuilder.new(env: env)
|
117
|
+
matcher = FileMatcher.new(targets: targets)
|
118
|
+
|
119
|
+
only = ENV['ONLY']&.then { Regexp.new(_1) } || //
|
120
|
+
|
121
|
+
update_targets = []
|
122
|
+
|
123
|
+
env.class_decls.each do |_name, entry|
|
124
|
+
entry.decls.each do |d|
|
125
|
+
decl = d.decl
|
126
|
+
loc = decl.location
|
127
|
+
fname = loc.name
|
128
|
+
next unless matcher.match?(fname)
|
129
|
+
|
130
|
+
process(decl, env: env, builder: builder, update_targets: update_targets)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
update!(update_targets: update_targets, only: only)
|
135
|
+
end
|
136
|
+
|
137
|
+
run(ARGV)
|
data/bin/rbs
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
root = Pathname(__dir__) / '../'
|
5
|
+
|
6
|
+
def v(require)
|
7
|
+
if v = ENV['RAILS_VERSION']
|
8
|
+
"#{require}:#{v}"
|
9
|
+
else
|
10
|
+
require
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def repo
|
15
|
+
ENV['RBS_REPO_DIR'] || Pathname(__dir__).join('../gem_rbs/gems').to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
exec(
|
19
|
+
'rbs',
|
20
|
+
# Require stdlibs
|
21
|
+
'-rlogger', '-rpathname', '-rmutex_m', '-rdate',
|
22
|
+
"--repo=#{repo}",
|
23
|
+
# Require Rails libraries
|
24
|
+
v('-ractivesupport'), v('-ractionpack'), v('-ractivejob'), v('-ractivemodel'), v('-ractionview'), v('-ractiverecord'), v('-rrailties'),
|
25
|
+
# Load signatures that are bundled in rbs_rails
|
26
|
+
'-I' + root.join('sig').to_s, '-I' + root.join('assets/sig').to_s,
|
27
|
+
# Expand arguments
|
28
|
+
*ARGV,
|
29
|
+
)
|
30
|
+
|
@@ -0,0 +1,195 @@
|
|
1
|
+
#!ruby
|
2
|
+
|
3
|
+
require 'rbs'
|
4
|
+
require 'rbs/cli'
|
5
|
+
|
6
|
+
using Module.new {
|
7
|
+
refine(Object) do
|
8
|
+
def const_name(node)
|
9
|
+
case node.type
|
10
|
+
when :CONST
|
11
|
+
node.children[0]
|
12
|
+
when :COLON2
|
13
|
+
base, name = node.children
|
14
|
+
base = const_name(base)
|
15
|
+
return unless base
|
16
|
+
"#{base}::#{name}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def process_class_methods(node, decls:, comments:, context:)
|
21
|
+
return false unless node.type == :ITER
|
22
|
+
|
23
|
+
fcall = node.children[0]
|
24
|
+
return false unless fcall.children[0] == :class_methods
|
25
|
+
|
26
|
+
name = RBS::TypeName.new(name: :ClassMethods, namespace: RBS::Namespace.empty)
|
27
|
+
mod = RBS::AST::Declarations::Module.new(
|
28
|
+
name: name,
|
29
|
+
type_params: RBS::AST::Declarations::ModuleTypeParams.empty,
|
30
|
+
self_types: [],
|
31
|
+
members: [],
|
32
|
+
annotations: [],
|
33
|
+
location: nil,
|
34
|
+
comment: comments[node.first_lineno - 1]
|
35
|
+
)
|
36
|
+
|
37
|
+
decls.push mod
|
38
|
+
|
39
|
+
each_node [node.children[1]] do |child|
|
40
|
+
process child, decls: mod.members, comments: comments, context: RBS::Prototype::RB::Context.initial
|
41
|
+
end
|
42
|
+
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_struct_new(node, decls:, comments:, context:)
|
47
|
+
return unless node.type == :CDECL
|
48
|
+
|
49
|
+
name, *_, rhs = node.children
|
50
|
+
fields, body = struct_new(rhs)
|
51
|
+
return unless fields
|
52
|
+
|
53
|
+
type_name = RBS::TypeName.new(name: name, namespace: RBS::Namespace.empty)
|
54
|
+
kls = RBS::AST::Declarations::Class.new(
|
55
|
+
name: type_name,
|
56
|
+
super_class: struct_as_superclass,
|
57
|
+
type_params: RBS::AST::Declarations::ModuleTypeParams.empty,
|
58
|
+
members: [],
|
59
|
+
annotations: [],
|
60
|
+
location: nil,
|
61
|
+
comment: comments[node.first_lineno - 1],
|
62
|
+
)
|
63
|
+
decls.push kls
|
64
|
+
|
65
|
+
fields.children.compact.each do |f|
|
66
|
+
case f.type
|
67
|
+
when :LIT, :STR
|
68
|
+
kls.members << RBS::AST::Members::AttrAccessor.new(
|
69
|
+
name: f.children.first,
|
70
|
+
type: untyped,
|
71
|
+
kind: :instance,
|
72
|
+
ivar_name: false,
|
73
|
+
annotations: [],
|
74
|
+
location: nil,
|
75
|
+
comment: nil,
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if body
|
81
|
+
each_node [body] do |child|
|
82
|
+
process child, decls: kls.members, comments: comments, context: RBS::Prototype::RB::Context.initial
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
def process_attr_internal(node, decls:, comments:, context:)
|
90
|
+
case node.type
|
91
|
+
when :FCALL, :VCALL
|
92
|
+
args = node.children[1]&.children || []
|
93
|
+
|
94
|
+
case node.children[0]
|
95
|
+
when :attr_internal_reader
|
96
|
+
args.each do |arg|
|
97
|
+
if arg && (name = literal_to_symbol(arg))
|
98
|
+
decls << RBS::AST::Members::AttrReader.new(
|
99
|
+
name: name,
|
100
|
+
ivar_name: :"@_#{name}",
|
101
|
+
type: RBS::Types::Bases::Any.new(location: nil),
|
102
|
+
kind: context.attribute_kind,
|
103
|
+
location: nil,
|
104
|
+
comment: comments[node.first_lineno - 1],
|
105
|
+
annotations: []
|
106
|
+
)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
when :attr_internal_writer
|
110
|
+
args.each do |arg|
|
111
|
+
if arg && (name = literal_to_symbol(arg))
|
112
|
+
decls << RBS::AST::Members::AttrWriter.new(
|
113
|
+
name: name,
|
114
|
+
ivar_name: :"@_#{name}",
|
115
|
+
type: RBS::Types::Bases::Any.new(location: nil),
|
116
|
+
kind: context.attribute_kind,
|
117
|
+
location: nil,
|
118
|
+
comment: comments[node.first_lineno - 1],
|
119
|
+
annotations: []
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
when :attr_internal_accessor, :attr_internal
|
124
|
+
args.each do |arg|
|
125
|
+
if arg && (name = literal_to_symbol(arg))
|
126
|
+
decls << RBS::AST::Members::AttrAccessor.new(
|
127
|
+
name: name,
|
128
|
+
ivar_name: :"@_#{name}",
|
129
|
+
type: RBS::Types::Bases::Any.new(location: nil),
|
130
|
+
kind: context.attribute_kind,
|
131
|
+
location: nil,
|
132
|
+
comment: comments[node.first_lineno - 1],
|
133
|
+
annotations: []
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def class_new_method_to_type(node)
|
142
|
+
case node.type
|
143
|
+
when :CALL
|
144
|
+
recv, name, _args = node.children
|
145
|
+
return unless name == :new
|
146
|
+
|
147
|
+
klass = const_name(recv)
|
148
|
+
return unless klass
|
149
|
+
|
150
|
+
type_name = RBS::TypeName.new(name: klass, namespace: RBS::Namespace.empty)
|
151
|
+
RBS::Types::ClassInstance.new(name: type_name, args: [], location: nil)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def struct_new(node)
|
156
|
+
case node.type
|
157
|
+
when :CALL
|
158
|
+
# ok
|
159
|
+
when :ITER
|
160
|
+
call, block = node.children
|
161
|
+
return struct_new(call)&.tap do |r|
|
162
|
+
r << block
|
163
|
+
end
|
164
|
+
else
|
165
|
+
return
|
166
|
+
end
|
167
|
+
|
168
|
+
recv, method_name, args = node.children
|
169
|
+
return unless method_name == :new
|
170
|
+
return unless recv.type == :CONST || recv.type == :COLON3
|
171
|
+
return unless recv.children.first == :Struct
|
172
|
+
|
173
|
+
[args]
|
174
|
+
end
|
175
|
+
|
176
|
+
def struct_as_superclass
|
177
|
+
name = RBS::TypeName.new(name: 'Struct', namespace: RBS::Namespace.root)
|
178
|
+
RBS::AST::Declarations::Class::Super.new(name: name, args: ['untyped'], location: nil)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
}
|
182
|
+
|
183
|
+
module PrototypeExt
|
184
|
+
def process(...)
|
185
|
+
process_class_methods(...) || process_struct_new(...) || process_attr_internal(...) || super
|
186
|
+
end
|
187
|
+
|
188
|
+
def literal_to_type(node)
|
189
|
+
class_new_method_to_type(node) || super
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
RBS::Prototype::RB.prepend PrototypeExt
|
194
|
+
|
195
|
+
RBS::CLI.new(stdout: STDOUT, stderr: STDERR).run(ARGV.dup)
|
data/bin/to-ascii.rb
ADDED
@@ -1,13 +1,12 @@
|
|
1
1
|
module RbsRails
|
2
2
|
module ActiveRecord
|
3
|
-
def self.class_to_rbs(klass
|
4
|
-
Generator.new(klass
|
3
|
+
def self.class_to_rbs(klass)
|
4
|
+
Generator.new(klass).generate
|
5
5
|
end
|
6
6
|
|
7
7
|
class Generator
|
8
|
-
def initialize(klass
|
8
|
+
def initialize(klass)
|
9
9
|
@klass = klass
|
10
|
-
@mode = mode
|
11
10
|
end
|
12
11
|
|
13
12
|
def generate
|
@@ -36,7 +35,7 @@ module RbsRails
|
|
36
35
|
<<~RBS
|
37
36
|
class #{relation_class_name} < ActiveRecord::Relation
|
38
37
|
include _ActiveRecord_Relation[#{klass.name}]
|
39
|
-
include Enumerable[#{klass.name}
|
38
|
+
include Enumerable[#{klass.name}]
|
40
39
|
#{enum_scope_methods(singleton: false).indent(2)}
|
41
40
|
#{scopes(singleton: false).indent(2)}
|
42
41
|
end
|
@@ -52,16 +51,9 @@ module RbsRails
|
|
52
51
|
|
53
52
|
|
54
53
|
private def header
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
when :class
|
59
|
-
# @type var superclass: Class
|
60
|
-
superclass = _ = klass.superclass
|
61
|
-
"class #{klass.name} < #{superclass.name}"
|
62
|
-
else
|
63
|
-
raise "unexpected mode: #{mode}"
|
64
|
-
end
|
54
|
+
# @type var superclass: Class
|
55
|
+
superclass = _ = klass.superclass
|
56
|
+
"class #{klass.name} < #{superclass.name}"
|
65
57
|
end
|
66
58
|
|
67
59
|
private def associations
|
@@ -74,21 +66,45 @@ module RbsRails
|
|
74
66
|
|
75
67
|
private def has_many
|
76
68
|
klass.reflect_on_all_associations(:has_many).map do |a|
|
77
|
-
|
69
|
+
singular_name = a.name.to_s.singularize
|
70
|
+
type = a.klass.name
|
71
|
+
collection_type = "#{type}::ActiveRecord_Associations_CollectionProxy"
|
72
|
+
<<~RUBY.chomp
|
73
|
+
def #{a.name}: () -> #{collection_type}
|
74
|
+
def #{a.name}=: (#{collection_type} | Array[#{type}]) -> (#{collection_type} | Array[#{type}])
|
75
|
+
def #{singular_name}_ids: () -> Array[Integer]
|
76
|
+
def #{singular_name}_ids=: (Array[Integer]) -> Array[Integer]
|
77
|
+
RUBY
|
78
78
|
end.join("\n")
|
79
79
|
end
|
80
80
|
|
81
81
|
private def has_one
|
82
82
|
klass.reflect_on_all_associations(:has_one).map do |a|
|
83
83
|
type = a.polymorphic? ? 'untyped' : a.klass.name
|
84
|
-
|
84
|
+
type_optional = optional(type)
|
85
|
+
<<~RUBY.chomp
|
86
|
+
def #{a.name}: () -> #{type}
|
87
|
+
def #{a.name}=: (#{type_optional}) -> #{type_optional}
|
88
|
+
def build_#{a.name}: (untyped) -> #{type}
|
89
|
+
def create_#{a.name}: (untyped) -> #{type}
|
90
|
+
def create_#{a.name}!: (untyped) -> #{type}
|
91
|
+
def reload_#{a.name}: () -> #{type_optional}
|
92
|
+
RUBY
|
85
93
|
end.join("\n")
|
86
94
|
end
|
87
95
|
|
88
96
|
private def belongs_to
|
89
97
|
klass.reflect_on_all_associations(:belongs_to).map do |a|
|
90
98
|
type = a.polymorphic? ? 'untyped' : a.klass.name
|
91
|
-
|
99
|
+
type_optional = optional(type)
|
100
|
+
<<~RUBY.chomp
|
101
|
+
def #{a.name}: () -> #{type}
|
102
|
+
def #{a.name}=: (#{type_optional}) -> #{type_optional}
|
103
|
+
def build_#{a.name}: (untyped) -> #{type}
|
104
|
+
def create_#{a.name}: (untyped) -> #{type}
|
105
|
+
def create_#{a.name}!: (untyped) -> #{type}
|
106
|
+
def reload_#{a.name}: () -> #{type_optional}
|
107
|
+
RUBY
|
92
108
|
end.join("\n")
|
93
109
|
end
|
94
110
|
|
@@ -138,6 +154,7 @@ module RbsRails
|
|
138
154
|
return [] unless ast
|
139
155
|
|
140
156
|
traverse(ast).map do |node|
|
157
|
+
# @type block: nil | Hash[untyped, untyped]
|
141
158
|
next unless node.type == :send
|
142
159
|
next unless node.children[0].nil?
|
143
160
|
next unless node.children[1] == :enum
|
@@ -176,6 +193,7 @@ module RbsRails
|
|
176
193
|
return '' unless ast
|
177
194
|
|
178
195
|
traverse(ast).map do |node|
|
196
|
+
# @type block: nil | String
|
179
197
|
next unless node.type == :send
|
180
198
|
next unless node.children[0].nil?
|
181
199
|
next unless node.children[1] == :scope
|
@@ -246,38 +264,65 @@ module RbsRails
|
|
246
264
|
|
247
265
|
private def columns
|
248
266
|
klass.columns.map do |col|
|
249
|
-
|
267
|
+
class_name = if enum_definitions.any? { |hash| hash.key?(col.name) || hash.key?(col.name.to_sym) }
|
268
|
+
'String'
|
269
|
+
else
|
270
|
+
sql_type_to_class(col.type)
|
271
|
+
end
|
272
|
+
class_name_opt = optional(class_name)
|
273
|
+
column_type = col.null ? class_name_opt : class_name
|
274
|
+
sig = <<~EOS
|
275
|
+
attr_accessor #{col.name} (): #{column_type}
|
276
|
+
def #{col.name}_changed?: () -> bool
|
277
|
+
def #{col.name}_change: () -> [#{class_name_opt}, #{class_name_opt}]
|
278
|
+
def #{col.name}_will_change!: () -> void
|
279
|
+
def #{col.name}_was: () -> #{class_name_opt}
|
280
|
+
def #{col.name}_previously_changed?: () -> bool
|
281
|
+
def #{col.name}_previous_change: () -> Array[#{class_name_opt}]?
|
282
|
+
def #{col.name}_previously_was: () -> #{class_name_opt}
|
283
|
+
def restore_#{col.name}!: () -> void
|
284
|
+
def clear_#{col.name}_change: () -> void
|
285
|
+
EOS
|
286
|
+
sig << "attr_accessor #{col.name}? (): #{class_name}\n" if col.type == :boolean
|
287
|
+
sig
|
250
288
|
end.join("\n")
|
251
289
|
end
|
252
290
|
|
291
|
+
private def optional(class_name)
|
292
|
+
class_name.include?("|") ? "(#{class_name})?" : "#{class_name}?"
|
293
|
+
end
|
294
|
+
|
253
295
|
private def sql_type_to_class(t)
|
254
296
|
case t
|
255
297
|
when :integer
|
256
|
-
Integer
|
298
|
+
'Integer'
|
257
299
|
when :float
|
258
|
-
Float
|
259
|
-
when :
|
260
|
-
|
300
|
+
'Float'
|
301
|
+
when :decimal
|
302
|
+
'BigDecimal'
|
303
|
+
when :string, :text, :citext, :uuid, :binary
|
304
|
+
'String'
|
261
305
|
when :datetime
|
262
|
-
|
263
|
-
# ActiveSupport::TimeWithZone.name
|
264
|
-
Time.name
|
306
|
+
'ActiveSupport::TimeWithZone'
|
265
307
|
when :boolean
|
266
|
-
"
|
308
|
+
"bool"
|
267
309
|
when :jsonb, :json
|
268
310
|
"untyped"
|
269
311
|
when :date
|
270
|
-
|
271
|
-
|
272
|
-
'
|
312
|
+
'Date'
|
313
|
+
when :time
|
314
|
+
'Time'
|
315
|
+
when :inet
|
316
|
+
"IPAddr"
|
273
317
|
else
|
274
|
-
|
318
|
+
# Unknown column type, give up
|
319
|
+
'untyped'
|
275
320
|
end
|
276
321
|
end
|
277
322
|
|
278
323
|
private
|
279
|
-
# @dynamic klass
|
280
|
-
attr_reader :klass
|
324
|
+
# @dynamic klass
|
325
|
+
attr_reader :klass
|
281
326
|
end
|
282
327
|
end
|
283
328
|
end
|