sorbet-rails 0.5.3 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +22 -1
- data/lib/sorbet-rails/actionmailer.rbi +7 -0
- data/lib/sorbet-rails/activerecord.rbi +3 -0
- data/lib/sorbet-rails/config.rb +5 -0
- data/lib/sorbet-rails/helper_rbi_formatter.rb +3 -0
- data/lib/sorbet-rails/mailer_rbi_formatter.rb +38 -0
- data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +26 -6
- data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +4 -5
- data/lib/sorbet-rails/model_plugins/active_record_enum.rb +37 -3
- data/lib/sorbet-rails/model_plugins/active_record_named_scope.rb +2 -0
- data/lib/sorbet-rails/model_plugins/active_record_overrides.rb +27 -0
- data/lib/sorbet-rails/model_plugins/active_storage_methods.rb +60 -0
- data/lib/sorbet-rails/model_plugins/plugins.rb +4 -0
- data/lib/sorbet-rails/sorbet_utils.rb +68 -0
- data/lib/sorbet-rails/tasks/rails_rbi.rake +20 -0
- data/lib/sorbet-rails/utils.rb +5 -0
- data/sorbet-rails.gemspec +1 -1
- data/spec/generators/rails-template.rb +145 -16
- data/spec/generators/sorbet_test_cases.rb +11 -0
- data/spec/helper_rbi_formatter_spec.rb +19 -0
- data/spec/mailer_rbi_formatter_spec.rb +13 -0
- data/spec/model_rbi_formatter_spec.rb +12 -2
- data/spec/rails_helper.rb +20 -0
- data/spec/rake_rails_rbi_mailers_spec.rb +21 -0
- data/spec/rake_rails_rbi_models_spec.rb +1 -21
- data/spec/sorbet_spec.rb +13 -0
- data/spec/sorbet_utils_spec.rb +126 -0
- data/spec/support/v4.2/Gemfile.lock +5 -5
- data/spec/support/v4.2/app/mailers/application_mailer.rb +3 -0
- data/spec/support/v4.2/app/mailers/daily_prophet_mailer.rb +9 -0
- data/spec/support/v4.2/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
- data/spec/support/v4.2/app/models/wizard.rb +14 -0
- data/spec/support/v4.2/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
- data/spec/support/v4.2/db/schema.rb +2 -1
- data/spec/support/v4.2/sorbet_test_cases.rb +11 -0
- data/spec/support/v5.0/Gemfile.lock +6 -6
- data/spec/support/v5.0/app/mailers/daily_prophet_mailer.rb +9 -0
- data/spec/support/v5.0/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
- data/spec/support/v5.0/app/models/wizard.rb +33 -0
- data/spec/support/v5.0/config/initializers/new_framework_defaults.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000001_create_wizards.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000002_create_wands.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000003_create_spell_books.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000004_add_more_column_types_to_wands.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
- data/spec/support/v5.0/db/migrate/20190620000006_add_more_enums_to_wizard.rb +9 -0
- data/spec/support/v5.0/db/schema.rb +9 -4
- data/spec/support/v5.0/sorbet_test_cases.rb +11 -0
- data/spec/support/v5.1/Gemfile.lock +6 -6
- data/spec/support/v5.1/app/mailers/daily_prophet_mailer.rb +9 -0
- data/spec/support/v5.1/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
- data/spec/support/v5.1/app/models/wizard.rb +33 -0
- data/spec/support/v5.1/db/migrate/20190620000001_create_wizards.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000002_create_wands.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000003_create_spell_books.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000004_add_more_column_types_to_wands.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
- data/spec/support/v5.1/db/migrate/20190620000006_add_more_enums_to_wizard.rb +9 -0
- data/spec/support/v5.1/db/schema.rb +7 -2
- data/spec/support/v5.1/sorbet_test_cases.rb +11 -0
- data/spec/support/v5.2/Gemfile.lock +6 -6
- data/spec/support/v5.2/app/mailers/daily_prophet_mailer.rb +9 -0
- data/spec/support/v5.2/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
- data/spec/support/v5.2/app/models/wizard.rb +34 -0
- data/spec/support/v5.2/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
- data/spec/support/v5.2/db/migrate/20190620000006_add_more_enums_to_wizard.rb +9 -0
- data/spec/support/v5.2/db/schema.rb +7 -2
- data/spec/support/v5.2/sorbet_test_cases.rb +11 -0
- data/spec/support/v6.0/Gemfile.lock +6 -6
- data/spec/support/v6.0/app/mailers/daily_prophet_mailer.rb +9 -0
- data/spec/support/v6.0/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
- data/spec/support/v6.0/app/models/wizard.rb +35 -1
- data/spec/support/v6.0/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
- data/spec/support/v6.0/db/migrate/20190620000006_add_more_enums_to_wizard.rb +9 -0
- data/spec/support/v6.0/db/schema.rb +6 -1
- data/spec/support/v6.0/sorbet_test_cases.rb +11 -0
- data/spec/test_data/v4.2/expected_application_mailer.rbi +5 -0
- data/spec/test_data/v4.2/expected_daily_prophet_mailer.rbi +7 -0
- data/spec/test_data/v4.2/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
- data/spec/test_data/v4.2/expected_hogwarts_acceptance_mailer.rbi +10 -0
- data/spec/test_data/v4.2/expected_srb_tc_output.txt +57 -1
- data/spec/test_data/v4.2/expected_wizard.rbi +69 -0
- data/spec/test_data/v4.2/expected_wizard_wo_spellbook.rbi +69 -0
- data/spec/test_data/v5.0/expected_application_mailer.rbi +5 -0
- data/spec/test_data/v5.0/expected_daily_prophet_mailer.rbi +7 -0
- data/spec/test_data/v5.0/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
- data/spec/test_data/v5.0/expected_hogwarts_acceptance_mailer.rbi +10 -0
- data/spec/test_data/v5.0/expected_spell_book.rbi +2 -2
- data/spec/test_data/v5.0/expected_wizard.rbi +294 -0
- data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +294 -0
- data/spec/test_data/v5.1/expected_application_mailer.rbi +5 -0
- data/spec/test_data/v5.1/expected_daily_prophet_mailer.rbi +7 -0
- data/spec/test_data/v5.1/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
- data/spec/test_data/v5.1/expected_hogwarts_acceptance_mailer.rbi +10 -0
- data/spec/test_data/v5.1/expected_spell_book.rbi +2 -2
- data/spec/test_data/v5.1/expected_wizard.rbi +294 -0
- data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +294 -0
- data/spec/test_data/v5.2-no-sorbet/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
- data/spec/test_data/v5.2/expected_application_mailer.rbi +5 -0
- data/spec/test_data/v5.2/expected_attachment.rbi +4 -4
- data/spec/test_data/v5.2/expected_daily_prophet_mailer.rbi +7 -0
- data/spec/test_data/v5.2/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
- data/spec/test_data/v5.2/expected_hogwarts_acceptance_mailer.rbi +10 -0
- data/spec/test_data/v5.2/expected_spell_book.rbi +2 -2
- data/spec/test_data/v5.2/expected_wizard.rbi +342 -0
- data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +342 -0
- data/spec/test_data/v6.0/expected_application_mailer.rbi +5 -0
- data/spec/test_data/v6.0/expected_attachment.rbi +4 -4
- data/spec/test_data/v6.0/expected_blob.rbi +28 -22
- data/spec/test_data/v6.0/expected_daily_prophet_mailer.rbi +7 -0
- data/spec/test_data/v6.0/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
- data/spec/test_data/v6.0/expected_hogwarts_acceptance_mailer.rbi +10 -0
- data/spec/test_data/v6.0/expected_spell_book.rbi +2 -2
- data/spec/test_data/v6.0/expected_wizard.rbi +526 -16
- data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +526 -16
- metadata +94 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b43d7a27b379e20892375b314afd08e4897ad6e0b20167b1a366de3121d0470
|
4
|
+
data.tar.gz: f6817dea5503132acc030da42ce68149675192c98536b8c795d8705a0e42649d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15251522124262b9c7aab57915d2cc5bc18e0807ccf27e2ea47e0d17173473a5d8a5a09ac4ea41e899de47a7972456ae060ef981e811e5f22095309c79d89b27
|
7
|
+
data.tar.gz: 7178e09505141a408617d2513f9da84ee8868edf5c808c3bca0ba293dbec4bb3cbbbe1362ad8b0af2f75f20862de4f95b67ddf6b1365a6d10111598bd9779b75
|
data/.gitignore
CHANGED
data/CONTRIBUTING.md
CHANGED
@@ -124,8 +124,8 @@ copied into each app with `cp`.
|
|
124
124
|
The `rails-template.rb` file uses the
|
125
125
|
[Rails Application Template](https://guides.rubyonrails.org/rails_application_templates.html)
|
126
126
|
functionality included in Rails. You can then regenerate each Rails app from
|
127
|
-
the same file using `bundle _1.17.3_ exec rake update_spec:
|
128
|
-
`bundle exec rake update_spec:
|
127
|
+
the same file using `bundle _1.17.3_ exec rake update_spec:v4_2`, `bundle exec rake update_spec:v5_0`,
|
128
|
+
`bundle exec rake update_spec:v5_1`, etc. (or just `bundle exec rake update_spec:v5_plus`,
|
129
129
|
though this excludes regenerating 4.2 because 4.2 blocks usage of Bundler 2.x).
|
130
130
|
|
131
131
|
#### Expected Output
|
data/README.md
CHANGED
@@ -30,6 +30,7 @@ gem 'sorbet-rails'
|
|
30
30
|
❯ rake rails_rbi:routes
|
31
31
|
❯ rake rails_rbi:models
|
32
32
|
❯ rake rails_rbi:helpers
|
33
|
+
❯ rake rails_rbi:mailers
|
33
34
|
|
34
35
|
# or run them all at once
|
35
36
|
❯ rake rails_rbi:all
|
@@ -76,12 +77,32 @@ It is possible to add custom RBI generation logic for your custom module or gems
|
|
76
77
|
|
77
78
|
### Helpers
|
78
79
|
|
79
|
-
This Rake task generates a `helpers.rbi` file that includes a basic module definition which includes the `Kernel` module
|
80
|
+
This Rake task generates a `helpers.rbi` file that includes a basic module definition which includes the `Kernel` module and `ActionView::Helpers`, to allow for some basic Ruby methods to be used in helpers without Sorbet complaining.
|
80
81
|
|
81
82
|
```sh
|
82
83
|
❯ rake rails_rbi:helpers
|
83
84
|
```
|
84
85
|
|
86
|
+
If you have additional modules that are included in all your helpers and you want `helpers.rbi` to reflect this, you can configure it:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
# -- config/initializers/sorbet_rails.rb
|
90
|
+
SorbetRails.configure do |config|
|
91
|
+
config.extra_helper_includes = ['ApplicationHelper', 'Devise::Controllers::Helpers']
|
92
|
+
end
|
93
|
+
|
94
|
+
### Mailers
|
95
|
+
|
96
|
+
This Rake task generates RBI files for all mailer classes in the Rails application (all descendants of `ActionMailer::Base`)
|
97
|
+
```sh
|
98
|
+
❯ rake rails_rbi:mailers
|
99
|
+
```
|
100
|
+
|
101
|
+
Since mailing action methods is based on instance methods defined in a mailer class, the signature of a mailing action method will be dependent on the signature the instance method has
|
102
|
+
- If there is a (sorbet) sig written for the instance method, it generates a matching sig for the mailing action method
|
103
|
+
- If not, all the params in the mailing action method will be T.untyped.
|
104
|
+
- For return type though, the mailing action method will return `ActionMailer::MessageDelivery` instead of the return type of the instance method.
|
105
|
+
|
85
106
|
## Tips & Tricks
|
86
107
|
|
87
108
|
### Overriding generated signatures
|
@@ -20,6 +20,9 @@ class ActiveRecord::Base < Object
|
|
20
20
|
|
21
21
|
sig { returns(T::Boolean) }
|
22
22
|
def self.abstract_class?; end
|
23
|
+
|
24
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
25
|
+
def self.attachment_reflections; end
|
23
26
|
end
|
24
27
|
|
25
28
|
class ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter;
|
data/lib/sorbet-rails/config.rb
CHANGED
@@ -34,6 +34,9 @@ module SorbetRails
|
|
34
34
|
sig { returns(T::Array[Symbol]) }
|
35
35
|
attr_accessor :enabled_model_plugins
|
36
36
|
|
37
|
+
sig { returns(T::Array[String]) }
|
38
|
+
attr_accessor :extra_helper_includes
|
39
|
+
|
37
40
|
sig { void }
|
38
41
|
def initialize
|
39
42
|
@enabled_gem_plugins = []
|
@@ -48,6 +51,8 @@ module SorbetRails
|
|
48
51
|
:custom_finder_methods,
|
49
52
|
:enumerable_collections,
|
50
53
|
]
|
54
|
+
@enabled_model_plugins << :active_storage_methods if defined?(T.unsafe(ActiveStorage))
|
55
|
+
@extra_helper_includes = []
|
51
56
|
end
|
52
57
|
|
53
58
|
sig { returns(T::Array[Symbol]) }
|
@@ -25,6 +25,9 @@ class SorbetRails::HelperRbiFormatter
|
|
25
25
|
@parlour.root.create_module(helper.to_s) do |mod|
|
26
26
|
mod.create_include('Kernel')
|
27
27
|
mod.create_include('ActionView::Helpers')
|
28
|
+
::SorbetRails.config.extra_helper_includes.each do |extra_helper|
|
29
|
+
mod.create_include(extra_helper) unless extra_helper == helper.to_s
|
30
|
+
end
|
28
31
|
end
|
29
32
|
end
|
30
33
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require('parlour')
|
3
|
+
require('sorbet-rails/sorbet_utils.rb')
|
4
|
+
|
5
|
+
class SorbetRails::MailerRbiFormatter
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(mailer_class: T.class_of(ActionMailer::Base)).void }
|
9
|
+
def initialize(mailer_class)
|
10
|
+
@mailer_class = T.let(mailer_class, T.class_of(ActionMailer::Base))
|
11
|
+
@parlour = T.let(Parlour::RbiGenerator.new, Parlour::RbiGenerator)
|
12
|
+
end
|
13
|
+
|
14
|
+
sig {returns(String)}
|
15
|
+
def generate_rbi
|
16
|
+
puts "-- Generate sigs for mailer #{@mailer_class.name} --"
|
17
|
+
|
18
|
+
@parlour.root.add_comments([
|
19
|
+
'This is an autogenerated file for Rails helpers.',
|
20
|
+
'Please rerun rake rails_rbi:mailers to regenerate.'
|
21
|
+
])
|
22
|
+
|
23
|
+
@parlour.root.create_class(@mailer_class.name) do |mailer_rbi|
|
24
|
+
@mailer_class.action_methods.to_a.sort.each do |mailer_method|
|
25
|
+
method_def = @mailer_class.instance_method(mailer_method)
|
26
|
+
parameters = SorbetRails::SorbetUtils.parameters_from_method_def(method_def)
|
27
|
+
mailer_rbi.create_method(
|
28
|
+
mailer_method,
|
29
|
+
parameters: parameters,
|
30
|
+
return_type: 'ActionMailer::MessageDelivery',
|
31
|
+
class_method: true,
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@parlour.rbi + "\n"
|
37
|
+
end
|
38
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# typed: true
|
2
2
|
require ('sorbet-rails/model_plugins/base')
|
3
3
|
class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::Base
|
4
|
-
sig {params(model_class: T.class_of(ActiveRecord::Base), available_classes: T::Set[String]).void}
|
4
|
+
sig { params(model_class: T.class_of(ActiveRecord::Base), available_classes: T::Set[String]).void }
|
5
5
|
def initialize(model_class, available_classes)
|
6
6
|
super
|
7
7
|
@columns_hash = @model_class.table_exists? ? @model_class.columns_hash : {}
|
@@ -25,16 +25,29 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
sig do
|
29
|
+
params(
|
30
|
+
assoc_module_rbi: T.untyped,
|
31
|
+
assoc_name: T.untyped,
|
32
|
+
reflection: T.untyped
|
33
|
+
).void
|
34
|
+
end
|
28
35
|
def populate_single_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection)
|
29
36
|
# TODO allow people to specify the possible values of polymorphic associations
|
30
37
|
assoc_class = assoc_should_be_untyped?(reflection) ? "T.untyped" : "::#{reflection.klass.name}"
|
31
38
|
assoc_type = "T.nilable(#{assoc_class})"
|
32
39
|
if reflection.belongs_to?
|
33
|
-
# if this is a belongs_to connection, we may be able to detect whether
|
34
|
-
# this field is required & use a stronger type
|
35
40
|
column_def = @columns_hash[reflection.foreign_key.to_s]
|
36
|
-
if
|
37
|
-
|
41
|
+
if ENV["RAILS_VERSION"] == "4.2"
|
42
|
+
# Before Rails 5, belongs_to relations were nilable by default
|
43
|
+
# if this is a belongs_to connection, we may be able to detect whether
|
44
|
+
# this field is required & use a stronger type
|
45
|
+
if column_def
|
46
|
+
assoc_type = assoc_class if !column_def.null
|
47
|
+
end
|
48
|
+
else
|
49
|
+
# In Rails 5 and later, belongs_to are required unless specified to be optional
|
50
|
+
assoc_type = assoc_class if !reflection.options[:optional]
|
38
51
|
end
|
39
52
|
end
|
40
53
|
|
@@ -51,6 +64,13 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
51
64
|
)
|
52
65
|
end
|
53
66
|
|
67
|
+
sig do
|
68
|
+
params(
|
69
|
+
assoc_module_rbi: T.untyped,
|
70
|
+
assoc_name: T.untyped,
|
71
|
+
reflection: T.untyped
|
72
|
+
).void
|
73
|
+
end
|
54
74
|
def populate_collection_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection)
|
55
75
|
# TODO allow people to specify the possible values of polymorphic associations
|
56
76
|
assoc_class = assoc_should_be_untyped?(reflection) ? "T.untyped" : "::#{reflection.klass.name}"
|
@@ -88,4 +108,4 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
88
108
|
polymorphic_assoc?(reflection.source_reflection) :
|
89
109
|
reflection.polymorphic?
|
90
110
|
end
|
91
|
-
end
|
111
|
+
end
|
@@ -55,7 +55,7 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
sig {params(column_def: T.untyped).returns(T.any(String, Class))}
|
58
|
+
sig { params(column_def: T.untyped).returns(T.any(String, Class)) }
|
59
59
|
def type_for_column_def(column_def)
|
60
60
|
cast_type = ActiveRecord::Base.connection.respond_to?(:lookup_cast_type_from_column) ?
|
61
61
|
ActiveRecord::Base.connection.lookup_cast_type_from_column(column_def) :
|
@@ -69,13 +69,12 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
|
|
69
69
|
column_def.null ? "T.nilable(#{strict_type})" : strict_type
|
70
70
|
end
|
71
71
|
|
72
|
-
sig
|
72
|
+
sig do
|
73
73
|
params(
|
74
74
|
# in v4.2, datetime can be TimeZoneConverter
|
75
75
|
klass: T.any(Object, ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter)
|
76
|
-
).
|
77
|
-
|
78
|
-
}
|
76
|
+
).returns(T.any(String, Class))
|
77
|
+
end
|
79
78
|
def active_record_type_to_sorbet_type(klass)
|
80
79
|
case klass
|
81
80
|
when ActiveRecord::Type::Boolean
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
require ('sorbet-rails/model_plugins/base')
|
3
|
+
require("sorbet-rails/utils")
|
3
4
|
class SorbetRails::ModelPlugins::ActiveRecordEnum < SorbetRails::ModelPlugins::Base
|
4
5
|
|
5
6
|
sig { implementation.params(root: Parlour::RbiGenerator::Namespace).void }
|
@@ -13,20 +14,53 @@ class SorbetRails::ModelPlugins::ActiveRecordEnum < SorbetRails::ModelPlugins::B
|
|
13
14
|
model_class_rbi = root.create_class(self.model_class_name)
|
14
15
|
model_class_rbi.create_include(enum_module_name)
|
15
16
|
|
17
|
+
enum_calls = ActiveRecordOverrides.instance.enum_calls[self.model_class_name]
|
18
|
+
|
16
19
|
# TODO: add any method for signature verification?
|
17
20
|
model_class.defined_enums.sort.each do |enum_name, enum_hash|
|
21
|
+
value_type = enum_hash.values.map { |v| v.is_a?(Integer) ? 'Integer' : v.class.name }.uniq
|
22
|
+
|
23
|
+
return_type = if value_type.length == 1
|
24
|
+
"T::Hash[T.any(String, Symbol), #{value_type.first}]"
|
25
|
+
else
|
26
|
+
"T::Hash[T.any(String, Symbol), T.any(#{value_type.join(", ")})]"
|
27
|
+
end
|
28
|
+
|
18
29
|
model_class_rbi.create_method(
|
19
30
|
enum_name.pluralize,
|
20
|
-
return_type:
|
31
|
+
return_type: return_type,
|
21
32
|
class_method: true,
|
22
33
|
)
|
34
|
+
|
35
|
+
enum_call = enum_calls.find {|call| call.has_key?(enum_name.to_sym)}
|
36
|
+
|
37
|
+
enum_prefix = enum_call[:_prefix]
|
38
|
+
prefix =
|
39
|
+
if enum_prefix == true
|
40
|
+
"#{enum_name}_"
|
41
|
+
elsif enum_prefix
|
42
|
+
"#{enum_prefix}_"
|
43
|
+
else
|
44
|
+
''
|
45
|
+
end
|
46
|
+
enum_suffix = enum_call[:_suffix]
|
47
|
+
suffix =
|
48
|
+
if enum_suffix == true
|
49
|
+
"_#{enum_name}"
|
50
|
+
elsif enum_suffix
|
51
|
+
"_#{enum_suffix}"
|
52
|
+
else
|
53
|
+
''
|
54
|
+
end
|
55
|
+
|
23
56
|
enum_hash.keys.each do |enum_val|
|
57
|
+
next unless SorbetRails::Utils.valid_method_name?(enum_val.to_s)
|
24
58
|
enum_module_rbi.create_method(
|
25
|
-
"#{enum_val}?",
|
59
|
+
"#{prefix}#{enum_val}#{suffix}?",
|
26
60
|
return_type: "T::Boolean",
|
27
61
|
)
|
28
62
|
enum_module_rbi.create_method(
|
29
|
-
"#{enum_val}!",
|
63
|
+
"#{prefix}#{enum_val}#{suffix}!",
|
30
64
|
return_type: nil, # void
|
31
65
|
)
|
32
66
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
require ('sorbet-rails/model_plugins/base')
|
3
|
+
require("sorbet-rails/utils")
|
3
4
|
class SorbetRails::ModelPlugins::ActiveRecordNamedScope < SorbetRails::ModelPlugins::Base
|
4
5
|
|
5
6
|
sig { implementation.params(root: Parlour::RbiGenerator::Namespace).void }
|
@@ -7,6 +8,7 @@ class SorbetRails::ModelPlugins::ActiveRecordNamedScope < SorbetRails::ModelPlug
|
|
7
8
|
model_class_rbi = root.create_class(self.model_class_name)
|
8
9
|
|
9
10
|
@model_class.methods.sort.each do |method_name|
|
11
|
+
next unless SorbetRails::Utils.valid_method_name?(method_name.to_s)
|
10
12
|
method_obj = @model_class.method(method_name)
|
11
13
|
next unless method_obj.present? && method_obj.source_location.present?
|
12
14
|
# we detect sscopes defined in a model by 2 criteria:
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
class ActiveRecordOverrides
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
attr_reader :enum_calls
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@enum_calls = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def store_enum_call(class_name, kwargs)
|
15
|
+
@enum_calls[class_name] ||= []
|
16
|
+
@enum_calls[class_name] << kwargs
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ::ActiveRecord::Enum
|
21
|
+
alias old_enum enum
|
22
|
+
|
23
|
+
def enum(*args, **kwargs)
|
24
|
+
ActiveRecordOverrides.instance.store_enum_call(T.unsafe(self).name, kwargs)
|
25
|
+
old_enum(*args, **kwargs)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# typed: true
|
2
|
+
require ('sorbet-rails/model_plugins/base')
|
3
|
+
class SorbetRails::ModelPlugins::ActiveStorageMethods < SorbetRails::ModelPlugins::Base
|
4
|
+
sig { params(model_class: T.class_of(ActiveRecord::Base), available_classes: T::Set[String]).void }
|
5
|
+
def initialize(model_class, available_classes)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
sig { implementation.params(root: Parlour::RbiGenerator::Namespace).void }
|
10
|
+
def generate(root)
|
11
|
+
# Check that ActiveStorage the attachment_reflections method exists
|
12
|
+
# It was added in 6.0, so it isn't available for 5.2.
|
13
|
+
return unless defined?(@model_class.attachment_reflections) && @model_class.attachment_reflections.length > 0
|
14
|
+
|
15
|
+
assoc_module_name = self.model_module_name("GeneratedAssociationMethods")
|
16
|
+
assoc_module_rbi = root.create_module(assoc_module_name)
|
17
|
+
|
18
|
+
attachment_reflections = @model_class.attachment_reflections.transform_values { |attachment| attachment.class }
|
19
|
+
|
20
|
+
attachment_reflections.each do |assoc_name, attachment_type|
|
21
|
+
if attachment_type.to_s == 'ActiveStorage::Reflection::HasOneAttachedReflection'
|
22
|
+
create_has_one_methods(assoc_name, assoc_module_rbi)
|
23
|
+
elsif attachment_type.to_s == 'ActiveStorage::Reflection::HasManyAttachedReflection'
|
24
|
+
create_has_many_methods(assoc_name, assoc_module_rbi)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { params(assoc_name: String, mod: Parlour::RbiGenerator::Namespace).void }
|
30
|
+
def create_has_one_methods(assoc_name, mod)
|
31
|
+
mod.create_method(
|
32
|
+
assoc_name,
|
33
|
+
return_type: 'T.nilable(ActiveStorage::Attached::One)'
|
34
|
+
)
|
35
|
+
|
36
|
+
mod.create_method(
|
37
|
+
"#{assoc_name}=",
|
38
|
+
parameters: [
|
39
|
+
Parameter.new('attachable', type: 'T.untyped')
|
40
|
+
],
|
41
|
+
return_type: 'T.untyped'
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { params(assoc_name: String, mod: Parlour::RbiGenerator::Namespace).void }
|
46
|
+
def create_has_many_methods(assoc_name, mod)
|
47
|
+
mod.create_method(
|
48
|
+
assoc_name,
|
49
|
+
return_type: 'T.nilable(ActiveStorage::Attached::Many)'
|
50
|
+
)
|
51
|
+
|
52
|
+
mod.create_method(
|
53
|
+
"#{assoc_name}=",
|
54
|
+
parameters: [
|
55
|
+
Parameter.new('*attachables', type: 'T.untyped')
|
56
|
+
],
|
57
|
+
return_type: 'T.untyped'
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
@@ -9,6 +9,8 @@ require('sorbet-rails/model_plugins/active_record_assoc')
|
|
9
9
|
require('sorbet-rails/model_plugins/active_record_finder_methods')
|
10
10
|
require('sorbet-rails/model_plugins/custom_finder_methods')
|
11
11
|
require('sorbet-rails/model_plugins/enumerable_collections')
|
12
|
+
require('sorbet-rails/model_plugins/active_record_overrides')
|
13
|
+
require('sorbet-rails/model_plugins/active_storage_methods')
|
12
14
|
|
13
15
|
module SorbetRails::ModelPlugins
|
14
16
|
extend T::Sig
|
@@ -57,6 +59,8 @@ module SorbetRails::ModelPlugins
|
|
57
59
|
CustomFinderMethods
|
58
60
|
when :enumerable_collections
|
59
61
|
EnumerableCollections
|
62
|
+
when :active_storage_methods
|
63
|
+
ActiveStorageMethods
|
60
64
|
when :kaminari
|
61
65
|
require('sorbet-rails/gem_plugins/kaminari_plugin')
|
62
66
|
KaminariPlugin
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
require('parlour')
|
4
|
+
require('sorbet-runtime')
|
5
|
+
|
6
|
+
module SorbetRails::SorbetUtils
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(method_def: UnboundMethod).returns(T::Array[Parlour::RbiGenerator::Parameter]) }
|
10
|
+
def self.parameters_from_method_def(method_def)
|
11
|
+
signature = T::Private::Methods.signature_for_method(method_def)
|
12
|
+
|
13
|
+
parameters_with_type = signature.nil? ?
|
14
|
+
method_def.parameters.map { |p| p + ['T.untyped'] } : # append untyped to each
|
15
|
+
get_ordered_parameters_with_type(signature)
|
16
|
+
|
17
|
+
parameters_with_type.map do |param_def|
|
18
|
+
param_name = param_def[1]
|
19
|
+
prefix =
|
20
|
+
case param_def[0]
|
21
|
+
when :rest; '*'
|
22
|
+
when :keyrest; '**'
|
23
|
+
when :block; '&'
|
24
|
+
# being comprehensive
|
25
|
+
when :req, :opt; ''
|
26
|
+
when :key, :keyreq; ''
|
27
|
+
else ''
|
28
|
+
end
|
29
|
+
|
30
|
+
param_type = param_def[2].to_s
|
31
|
+
if param_def[0] == :block
|
32
|
+
# special case `.void` in a proc
|
33
|
+
# see https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/types/proc.rb#L10
|
34
|
+
param_type = param_type.gsub('returns(<VOID>)', 'void')
|
35
|
+
end
|
36
|
+
|
37
|
+
::Parlour::RbiGenerator::Parameter.new("#{prefix}#{param_name}", type: param_type)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
sig {
|
42
|
+
params(signature: T::Private::Methods::Signature).
|
43
|
+
returns(T::Array[[Symbol, Symbol, T::Types::Base]])
|
44
|
+
}
|
45
|
+
def self.get_ordered_parameters_with_type(signature)
|
46
|
+
# extract original method param from signature
|
47
|
+
# https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/private/methods/signature.rb#L5-L8
|
48
|
+
params = []
|
49
|
+
signature.arg_types.each do |arg_type|
|
50
|
+
# could be :opt, but doesn't matter
|
51
|
+
params << [:req, arg_type[0], arg_type[1]]
|
52
|
+
end
|
53
|
+
signature.req_kwarg_names.each do |kwarg_name|
|
54
|
+
# could be :key, but doesn't matter
|
55
|
+
params << [:keyreq, kwarg_name, signature.kwarg_types[kwarg_name]]
|
56
|
+
end
|
57
|
+
if signature.has_rest
|
58
|
+
params << [:rest, signature.rest_name, signature.rest_type]
|
59
|
+
end
|
60
|
+
if signature.has_keyrest
|
61
|
+
params << [:keyrest, signature.keyrest_name, signature.keyrest_type]
|
62
|
+
end
|
63
|
+
if !signature.block_name.nil?
|
64
|
+
params << [:block, signature.block_name, signature.block_type]
|
65
|
+
end
|
66
|
+
params
|
67
|
+
end
|
68
|
+
end
|