sorbet-rails 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/README.md +3 -3
- data/lib/bundled_rbi/typed_enum.rbi +7 -0
- data/lib/sorbet-rails.rb +0 -2
- data/lib/sorbet-rails/active_record_rbi_formatter.rb +301 -0
- data/lib/sorbet-rails/config.rb +0 -1
- data/lib/sorbet-rails/dependent_gem_rbis/activerecord.rbi +11 -0
- data/lib/sorbet-rails/deprecation.rb +5 -0
- data/lib/sorbet-rails/gem_plugins/active_flag_plugin.rb +0 -1
- data/lib/sorbet-rails/gem_plugins/paperclip_plugin.rb +0 -1
- data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +36 -8
- data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +20 -9
- data/lib/sorbet-rails/model_plugins/active_record_enum.rb +0 -1
- data/lib/sorbet-rails/model_plugins/active_record_named_scope.rb +15 -6
- data/lib/sorbet-rails/model_plugins/active_record_querying.rb +34 -3
- data/lib/sorbet-rails/model_plugins/active_storage_methods.rb +1 -1
- data/lib/sorbet-rails/model_plugins/base.rb +10 -0
- data/lib/sorbet-rails/model_plugins/enumerable_collections.rb +0 -50
- data/lib/sorbet-rails/model_plugins/plugins.rb +0 -3
- data/lib/sorbet-rails/model_rbi_formatter.rb +6 -10
- data/lib/sorbet-rails/model_utils.rb +83 -36
- data/lib/sorbet-rails/rails_mixins/generated_url_helpers.rb +2 -3
- data/lib/sorbet-rails/railtie.rb +0 -2
- data/lib/sorbet-rails/tasks/rails_rbi.rake +32 -24
- data/sorbet-rails.gemspec +2 -2
- data/spec/active_record_rbi_formatter_spec.rb +24 -0
- data/spec/generators/rails-template.rb +90 -6
- data/spec/generators/sorbet_test_cases.rb +204 -122
- data/spec/model_rbi_formatter_spec.rb +1 -1
- data/spec/rails_helper.rb +14 -1
- data/spec/rake_rails_rbi_active_record_spec.rb +21 -0
- data/spec/rake_rails_rbi_models_spec.rb +7 -0
- data/spec/sorbet_spec.rb +12 -1
- data/spec/support/v5.0/Gemfile.lock +8 -8
- data/spec/support/v5.0/app/models/headmaster.rb +8 -0
- data/spec/support/v5.0/app/models/school.rb +2 -0
- data/spec/support/v5.0/app/models/spell.rb +5 -0
- data/spec/support/v5.0/app/models/spell_book.rb +5 -0
- data/spec/support/v5.0/app/models/subject.rb +5 -0
- data/spec/support/v5.0/app/models/wizard.rb +6 -1
- data/spec/support/v5.0/db/migrate/20190620000010_add_subject.rb +8 -0
- data/spec/support/v5.0/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
- data/spec/support/v5.0/db/migrate/20190620000012_add_spell.rb +8 -0
- data/spec/support/v5.0/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
- data/spec/support/v5.0/db/migrate/20190620000014_create_headmasters.rb +9 -0
- data/spec/support/v5.0/db/schema.rb +28 -1
- data/spec/support/v5.0/lib/mythical_rbi_plugin.rb +1 -1
- data/spec/support/v5.0/sorbet_test_cases.rb +204 -122
- data/spec/support/v5.1/Gemfile.lock +8 -8
- data/spec/support/v5.1/app/models/headmaster.rb +8 -0
- data/spec/support/v5.1/app/models/school.rb +2 -0
- data/spec/support/v5.1/app/models/spell.rb +5 -0
- data/spec/support/v5.1/app/models/spell_book.rb +5 -0
- data/spec/support/v5.1/app/models/subject.rb +5 -0
- data/spec/support/v5.1/app/models/wizard.rb +6 -1
- data/spec/support/v5.1/db/migrate/20190620000010_add_subject.rb +8 -0
- data/spec/support/v5.1/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
- data/spec/support/v5.1/db/migrate/20190620000012_add_spell.rb +8 -0
- data/spec/support/v5.1/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
- data/spec/support/v5.1/db/migrate/20190620000014_create_headmasters.rb +9 -0
- data/spec/support/v5.1/db/schema.rb +28 -1
- data/spec/support/v5.1/lib/mythical_rbi_plugin.rb +1 -1
- data/spec/support/v5.1/sorbet_test_cases.rb +204 -122
- data/spec/support/v5.2/Gemfile +1 -1
- data/spec/support/v5.2/Gemfile.lock +9 -9
- data/spec/support/v5.2/app/models/headmaster.rb +8 -0
- data/spec/support/v5.2/app/models/school.rb +2 -0
- data/spec/support/v5.2/app/models/spell.rb +5 -0
- data/spec/support/v5.2/app/models/spell_book.rb +5 -0
- data/spec/support/v5.2/app/models/subject.rb +5 -0
- data/spec/support/v5.2/app/models/wizard.rb +5 -0
- data/spec/support/v5.2/config/puma.rb +3 -0
- data/spec/support/v5.2/db/migrate/20190620000010_add_subject.rb +8 -0
- data/spec/support/v5.2/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
- data/spec/support/v5.2/db/migrate/20190620000012_add_spell.rb +8 -0
- data/spec/support/v5.2/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
- data/spec/support/v5.2/db/migrate/20190620000014_create_headmasters.rb +9 -0
- data/spec/support/v5.2/db/schema.rb +28 -1
- data/spec/support/v5.2/lib/mythical_rbi_plugin.rb +1 -1
- data/spec/support/v5.2/sorbet_test_cases.rb +204 -122
- data/spec/support/v6.0/Gemfile.lock +8 -8
- data/spec/support/v6.0/app/models/headmaster.rb +8 -0
- data/spec/support/v6.0/app/models/school.rb +2 -0
- data/spec/support/v6.0/app/models/spell.rb +5 -0
- data/spec/support/v6.0/app/models/spell_book.rb +5 -0
- data/spec/support/v6.0/app/models/subject.rb +5 -0
- data/spec/support/v6.0/app/models/wizard.rb +6 -1
- data/spec/support/v6.0/db/migrate/20190620000010_add_subject.rb +8 -0
- data/spec/support/v6.0/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
- data/spec/support/v6.0/db/migrate/20190620000012_add_spell.rb +8 -0
- data/spec/support/v6.0/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
- data/spec/support/v6.0/db/migrate/20190620000014_create_headmasters.rb +9 -0
- data/spec/support/v6.0/db/schema.rb +28 -1
- data/spec/support/v6.0/lib/mythical_rbi_plugin.rb +1 -1
- data/spec/support/v6.0/sorbet_test_cases.rb +204 -122
- data/spec/test_data/v5.0/expected_active_record_base.rbi +113 -0
- data/spec/test_data/v5.0/expected_active_record_relation.rbi +199 -0
- data/spec/test_data/v5.0/expected_habtm_subjects.rbi +660 -0
- data/spec/test_data/v5.0/expected_habtm_wizards.rbi +660 -0
- data/spec/test_data/v5.0/expected_headmaster.rbi +304 -0
- data/spec/test_data/v5.0/expected_internal_metadata.rbi +38 -401
- data/spec/test_data/v5.0/expected_potion.rbi +38 -401
- data/spec/test_data/v5.0/expected_robe.rbi +38 -403
- data/spec/test_data/v5.0/expected_schema_migration.rbi +38 -401
- data/spec/test_data/v5.0/expected_school.rbi +47 -401
- data/spec/test_data/v5.0/expected_spell.rbi +292 -0
- data/spec/test_data/v5.0/expected_spell/habtm_spell_books.rbi +295 -0
- data/spec/test_data/v5.0/expected_spell_book.rbi +86 -432
- data/spec/test_data/v5.0/expected_spell_book/habtm_spell_books.rbi +637 -0
- data/spec/test_data/v5.0/expected_spell_book/habtm_spells.rbi +295 -0
- data/spec/test_data/v5.0/expected_squib.rbi +122 -477
- data/spec/test_data/v5.0/expected_subject.rbi +292 -0
- data/spec/test_data/v5.0/expected_subject/habtm_wizards.rbi +295 -0
- data/spec/test_data/v5.0/expected_wand.rbi +75 -442
- data/spec/test_data/v5.0/expected_wizard.rbi +144 -498
- data/spec/test_data/v5.0/expected_wizard/habtm_subjects.rbi +295 -0
- data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +138 -498
- data/spec/test_data/v5.1/expected_active_record_base.rbi +113 -0
- data/spec/test_data/v5.1/expected_active_record_relation.rbi +178 -0
- data/spec/test_data/v5.1/expected_habtm_subjects.rbi +672 -0
- data/spec/test_data/v5.1/expected_habtm_wizards.rbi +672 -0
- data/spec/test_data/v5.1/expected_headmaster.rbi +310 -0
- data/spec/test_data/v5.1/expected_internal_metadata.rbi +38 -407
- data/spec/test_data/v5.1/expected_potion.rbi +38 -407
- data/spec/test_data/v5.1/expected_robe.rbi +38 -409
- data/spec/test_data/v5.1/expected_schema_migration.rbi +38 -407
- data/spec/test_data/v5.1/expected_school.rbi +47 -407
- data/spec/test_data/v5.1/expected_spell.rbi +298 -0
- data/spec/test_data/v5.1/expected_spell/habtm_spell_books.rbi +301 -0
- data/spec/test_data/v5.1/expected_spell_book.rbi +86 -438
- data/spec/test_data/v5.1/expected_spell_book/habtm_spell_books.rbi +649 -0
- data/spec/test_data/v5.1/expected_spell_book/habtm_spells.rbi +301 -0
- data/spec/test_data/v5.1/expected_squib.rbi +123 -484
- data/spec/test_data/v5.1/expected_subject.rbi +298 -0
- data/spec/test_data/v5.1/expected_subject/habtm_wizards.rbi +301 -0
- data/spec/test_data/v5.1/expected_wand.rbi +75 -448
- data/spec/test_data/v5.1/expected_wizard.rbi +145 -505
- data/spec/test_data/v5.1/expected_wizard/habtm_subjects.rbi +301 -0
- data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +139 -505
- data/spec/test_data/v5.2/expected_active_record_base.rbi +113 -0
- data/spec/test_data/v5.2/expected_active_record_relation.rbi +175 -0
- data/spec/test_data/v5.2/expected_attachment.rbi +38 -407
- data/spec/test_data/v5.2/expected_blob.rbi +60 -426
- data/spec/test_data/v5.2/expected_habtm_subjects.rbi +672 -0
- data/spec/test_data/v5.2/expected_habtm_wizards.rbi +672 -0
- data/spec/test_data/v5.2/expected_headmaster.rbi +310 -0
- data/spec/test_data/v5.2/expected_internal_metadata.rbi +38 -407
- data/spec/test_data/v5.2/expected_potion.rbi +38 -407
- data/spec/test_data/v5.2/expected_robe.rbi +38 -409
- data/spec/test_data/v5.2/expected_schema_migration.rbi +38 -407
- data/spec/test_data/v5.2/expected_school.rbi +47 -407
- data/spec/test_data/v5.2/expected_spell.rbi +298 -0
- data/spec/test_data/v5.2/expected_spell/habtm_spell_books.rbi +301 -0
- data/spec/test_data/v5.2/expected_spell_book.rbi +86 -438
- data/spec/test_data/v5.2/expected_spell_book/habtm_spell_books.rbi +649 -0
- data/spec/test_data/v5.2/expected_spell_book/habtm_spells.rbi +301 -0
- data/spec/test_data/v5.2/expected_squib.rbi +133 -488
- data/spec/test_data/v5.2/expected_subject.rbi +298 -0
- data/spec/test_data/v5.2/expected_subject/habtm_wizards.rbi +301 -0
- data/spec/test_data/v5.2/expected_wand.rbi +75 -448
- data/spec/test_data/v5.2/expected_wizard.rbi +155 -509
- data/spec/test_data/v5.2/expected_wizard/habtm_subjects.rbi +301 -0
- data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +149 -509
- data/spec/test_data/v6.0/expected_active_record_base.rbi +113 -0
- data/spec/test_data/v6.0/expected_active_record_relation.rbi +175 -0
- data/spec/test_data/v6.0/expected_attachment.rbi +38 -431
- data/spec/test_data/v6.0/expected_blob.rbi +60 -450
- data/spec/test_data/v6.0/expected_habtm_subjects.rbi +720 -0
- data/spec/test_data/v6.0/expected_habtm_wizards.rbi +720 -0
- data/spec/test_data/v6.0/expected_headmaster.rbi +334 -0
- data/spec/test_data/v6.0/expected_internal_metadata.rbi +38 -431
- data/spec/test_data/v6.0/expected_potion.rbi +38 -431
- data/spec/test_data/v6.0/expected_robe.rbi +38 -433
- data/spec/test_data/v6.0/expected_routes.rbi +14 -7
- data/spec/test_data/v6.0/expected_schema_migration.rbi +38 -431
- data/spec/test_data/v6.0/expected_school.rbi +47 -431
- data/spec/test_data/v6.0/expected_spell.rbi +322 -0
- data/spec/test_data/v6.0/expected_spell/habtm_spell_books.rbi +325 -0
- data/spec/test_data/v6.0/expected_spell_book.rbi +98 -474
- data/spec/test_data/v6.0/expected_spell_book/habtm_spell_books.rbi +697 -0
- data/spec/test_data/v6.0/expected_spell_book/habtm_spells.rbi +325 -0
- data/spec/test_data/v6.0/expected_squib.rbi +162 -541
- data/spec/test_data/v6.0/expected_subject.rbi +322 -0
- data/spec/test_data/v6.0/expected_subject/habtm_wizards.rbi +325 -0
- data/spec/test_data/v6.0/expected_wand.rbi +91 -488
- data/spec/test_data/v6.0/expected_wizard.rbi +184 -562
- data/spec/test_data/v6.0/expected_wizard/habtm_subjects.rbi +325 -0
- data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +178 -562
- metadata +170 -24
- data/lib/bundled_rbi/active_record_base.rbi +0 -83
- data/lib/bundled_rbi/active_record_relation.rbi +0 -122
- data/lib/bundled_rbi/parameters.rbi +0 -28
- data/lib/sorbet-rails/custom_types/boolean_string.rb +0 -32
- data/lib/sorbet-rails/custom_types/integer_string.rb +0 -35
- data/lib/sorbet-rails/model_plugins/active_record_finder_methods.rb +0 -131
- data/lib/sorbet-rails/rails_mixins/custom_params_methods.rb +0 -46
- data/spec/boolean_string_spec.rb +0 -59
- data/spec/custom_params_methods_spec.rb +0 -138
- data/spec/integer_string_spec.rb +0 -46
- data/spec/support/v5.0/typed-override.yaml +0 -2
- data/spec/support/v5.1/typed-override.yaml +0 -2
- data/spec/support/v5.2/typed-override.yaml +0 -2
- data/spec/support/v6.0/typed-override.yaml +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 573cda715fb4ceb9881c61dce1f346499a60202ac181548727d7519f8f1b24fe
|
4
|
+
data.tar.gz: ba219d4d0ec79076e4a93cf2b6c0eb40272489fb4b71d42eea881c6c7c124b6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6d81c40b7c86667834b5063637e9ed29204fda6494aaeccd59781ae481849d7e90b4f5edf678b6727197e6f194c3b06bc87c276e7801013343f2e698c904e3e
|
7
|
+
data.tar.gz: 11b034a0b6691ae3d9baf8a2c285b3277c99aa25d280eba8bf12bc78b5a443a4d5d502e814edf1eb576c1bbc108301cfcc0b240cff354a03163497e109ca403c
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -73,7 +73,7 @@ It is possible to add custom RBI generation logic for your custom module or gems
|
|
73
73
|
We also add following methods to make type-checking more easily:
|
74
74
|
- [`find_n`, `first_n`, `last_n`](https://github.com/chanzuckerberg/sorbet-rails#find-first-and-last)
|
75
75
|
- [`pluck_to_tstruct`](#pluck_to_tstruct-instead-of-pluck)
|
76
|
-
- [`typed_enum`](#
|
76
|
+
- [`typed_enum`](#typed_enum-instead-of-enum)
|
77
77
|
- [`Model::RelationType`](#relationtype-alias)
|
78
78
|
|
79
79
|
#### `pluck_to_tstruct` instead of `pluck`
|
@@ -96,7 +96,7 @@ Wizard.all.pluck_to_tstruct(TA[WizardStruct].new) # T::Array[WizardStruct]
|
|
96
96
|
|
97
97
|
This method is based on [pluck_to_hash](https://github.com/girishso/pluck_to_hash) gem.
|
98
98
|
|
99
|
-
####
|
99
|
+
#### `typed_enum` instead of `enum`
|
100
100
|
|
101
101
|
If you use [Rails `enum`](https://guides.rubyonrails.org/active_record_querying.html#enums), `sorbet-rails` will generate a corresponding `T::Enum`. It will also include getters, setters, and scope methods in the rbi file it generates.
|
102
102
|
|
@@ -203,7 +203,7 @@ end
|
|
203
203
|
If it fails to coerce the params into the right type, an `ActionController::BadRequest` exception will be raised with the coercion context for debugging.
|
204
204
|
|
205
205
|
Note: The API `TypedParams[...].new.extract!` may seem verbose, but necessary to support this feature. Ideally, the API can be simply `TypedParams.extract!(...)`. However, `sorbet` [doesn't support](http://github.com/sorbet/sorbet/issues/62) defining a method that accept a type and return an instance of the type. If this feature is supported by `sorbet` in the future, it will be easy to codemod to be `TypedParams.extract(...)!` part from your code.
|
206
|
-
Note: [`require_typed` and `fetch_typed`](https://github.com/chanzuckerberg/sorbet-rails/blob/v0.5.9.1/README.md) are deprecated in favor of `TypedParams`.
|
206
|
+
Note: [`require_typed` and `fetch_typed`](https://github.com/chanzuckerberg/sorbet-rails/blob/v0.5.9.1/README.md) are deprecated in favor of `TypedParams`. They will be removed in v0.7.
|
207
207
|
|
208
208
|
### Routes
|
209
209
|
|
data/lib/sorbet-rails.rb
CHANGED
@@ -4,8 +4,6 @@ module SorbetRails
|
|
4
4
|
require 'sorbet-rails/railtie'
|
5
5
|
require 'sorbet-rails/model_rbi_formatter'
|
6
6
|
require 'sorbet-rails/type_assert/type_assert'
|
7
|
-
require 'sorbet-rails/custom_types/integer_string'
|
8
|
-
require 'sorbet-rails/custom_types/boolean_string'
|
9
7
|
require 'sorbet-rails/typed_params'
|
10
8
|
end
|
11
9
|
end
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require('parlour')
|
3
|
+
|
4
|
+
class SorbetRails::ActiveRecordRbiFormatter
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
Parameter = ::Parlour::RbiGenerator::Parameter
|
8
|
+
|
9
|
+
sig {returns(String)}
|
10
|
+
def generate_active_record_base_rbi
|
11
|
+
puts "-- Generate sigs for ActiveRecord::Base --"
|
12
|
+
|
13
|
+
parlour = T.let(Parlour::RbiGenerator.new, Parlour::RbiGenerator)
|
14
|
+
|
15
|
+
parlour.root.add_comments([
|
16
|
+
'This is an autogenerated file for Rails\' ActiveRecord.',
|
17
|
+
'Please rerun bundle exec rake rails_rbi:active_record to regenerate.'
|
18
|
+
])
|
19
|
+
|
20
|
+
parlour.root.create_class('ActiveRecord::Base') do |class_rbi|
|
21
|
+
create_elem_specific_query_methods(class_rbi, type: 'T.attached_class', class_method: true)
|
22
|
+
create_general_query_methods(class_rbi, class_method: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
parlour.rbi
|
26
|
+
end
|
27
|
+
|
28
|
+
sig {returns(String)}
|
29
|
+
def generate_active_record_relation_rbi
|
30
|
+
puts "-- Generate sigs for ActiveRecord::Relation --"
|
31
|
+
|
32
|
+
parlour = T.let(Parlour::RbiGenerator.new, Parlour::RbiGenerator)
|
33
|
+
|
34
|
+
parlour.root.add_comments([
|
35
|
+
'This is an autogenerated file for Rails\' ActiveRecord.',
|
36
|
+
'Please rerun bundle exec rake rails_rbi:active_record to regenerate.'
|
37
|
+
])
|
38
|
+
|
39
|
+
parlour.root.create_class('ActiveRecord::Relation') do |class_rbi|
|
40
|
+
class_rbi.create_include("Enumerable")
|
41
|
+
class_rbi.create_constant(
|
42
|
+
"Elem",
|
43
|
+
value: "type_member(fixed: T.untyped)",
|
44
|
+
)
|
45
|
+
|
46
|
+
create_elem_specific_query_methods(class_rbi, type: 'Elem', class_method: false)
|
47
|
+
create_general_query_methods(class_rbi, class_method: false)
|
48
|
+
|
49
|
+
# Many methods that exist on the relation classes also exist on the model class
|
50
|
+
# by delegating to `:all` (e.g. `Model.any?` is really `Model.all.any?`). These
|
51
|
+
# methods (e.g. each, empty?) only exist on the relation classes.
|
52
|
+
class_rbi.create_method(
|
53
|
+
"each",
|
54
|
+
parameters: [
|
55
|
+
Parameter.new("&block", type: "T.proc.params(e: Elem).void")
|
56
|
+
],
|
57
|
+
return_type: "T::Array[Elem]",
|
58
|
+
implementation: true,
|
59
|
+
)
|
60
|
+
class_rbi.create_method(
|
61
|
+
"flatten",
|
62
|
+
parameters: [ Parameter.new("level", type: "T.nilable(Integer)") ],
|
63
|
+
return_type: "T::Array[Elem]",
|
64
|
+
)
|
65
|
+
class_rbi.create_method("to_a", return_type: "T::Array[Elem]")
|
66
|
+
class_rbi.create_method(
|
67
|
+
"map",
|
68
|
+
type_parameters: [:U],
|
69
|
+
parameters: [ Parameter.new("&blk", type: "T.proc.params(arg0: Elem).returns(T.type_parameter(:U))") ],
|
70
|
+
return_type: "T::Array[T.type_parameter(:U)]",
|
71
|
+
)
|
72
|
+
class_rbi.create_method('empty?', return_type: "T::Boolean")
|
73
|
+
end
|
74
|
+
|
75
|
+
parlour.root.create_class("ActiveRecord::AssociationRelation", superclass: "ActiveRecord::Relation") do |class_rbi|
|
76
|
+
class_rbi.create_constant(
|
77
|
+
"Elem",
|
78
|
+
value: "type_member(fixed: T.untyped)",
|
79
|
+
)
|
80
|
+
|
81
|
+
# Ideally we shouldn't need to define these since this class inherits from
|
82
|
+
# ActiveRecord::Relation but the activerecord.rbi that sorbet generates
|
83
|
+
# defines some methods which sorbet finds instead of the methods inherited
|
84
|
+
# by ActiveRecord::Relation. Some of these methods have different arity or
|
85
|
+
# parameters than the ones defined by `create_elem_specific_query_methods` so
|
86
|
+
# we need to match the signatures in that conflicting rbi.
|
87
|
+
build_methods = %w(new build create create!)
|
88
|
+
# This needs to match the generated method signature in activerecord.rbi and
|
89
|
+
# in Rails 5.0 and 5.1 the param is a splat.
|
90
|
+
if Rails.version =~ /^5\./
|
91
|
+
build_param = Parameter.new("*args", type: "T.untyped")
|
92
|
+
else
|
93
|
+
build_param = Parameter.new("attributes", type: "T.untyped", default: 'nil')
|
94
|
+
end
|
95
|
+
build_methods.each do |build_method|
|
96
|
+
class_rbi.create_method(
|
97
|
+
build_method,
|
98
|
+
parameters: [
|
99
|
+
build_param,
|
100
|
+
Parameter.new(
|
101
|
+
"&block",
|
102
|
+
type: "T.nilable(T.proc.params(object: Elem).void)",
|
103
|
+
),
|
104
|
+
],
|
105
|
+
return_type: "Elem",
|
106
|
+
)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
parlour.root.create_class("ActiveRecord::Associations::CollectionProxy", superclass: "ActiveRecord::Relation") do |class_rbi|
|
111
|
+
class_rbi.create_constant(
|
112
|
+
"Elem",
|
113
|
+
value: "type_member(fixed: T.untyped)",
|
114
|
+
)
|
115
|
+
|
116
|
+
# This _should_ work which would let us remove it from the enumerable_collections
|
117
|
+
# plugin but sorbet has a bug with T.any and generics.
|
118
|
+
# See: https://github.com/sorbet/sorbet/issues/2938
|
119
|
+
# push_methods = %w(<< append push concat)
|
120
|
+
# push_methods.each do |push_method|
|
121
|
+
# class_rbi.create_method(
|
122
|
+
# push_method,
|
123
|
+
# parameters: [
|
124
|
+
# Parameter.new("*records", type: "T.any(Elem, T::Array[Elem])"),
|
125
|
+
# ],
|
126
|
+
# return_type: "T.self_type",
|
127
|
+
# )
|
128
|
+
# end
|
129
|
+
|
130
|
+
# Ideally we shouldn't need to define these since this class inherits from
|
131
|
+
# ActiveRecord::Relation but the activerecord.rbi that sorbet generates
|
132
|
+
# defines some methods which sorbet finds instead of the methods inherited
|
133
|
+
# by ActiveRecord::Relation. Some of these methods have different arity or
|
134
|
+
# parameters than the ones defined by `create_elem_specific_query_methods` so
|
135
|
+
# we need to match the signatures in that conflicting rbi.
|
136
|
+
build_methods = %w(new build create create!)
|
137
|
+
build_methods.each do |build_method|
|
138
|
+
class_rbi.create_method(
|
139
|
+
build_method,
|
140
|
+
parameters: [
|
141
|
+
Parameter.new("attributes", type: "T.untyped", default: 'nil'),
|
142
|
+
Parameter.new(
|
143
|
+
"&block",
|
144
|
+
type: "T.nilable(T.proc.params(object: Elem).void)",
|
145
|
+
),
|
146
|
+
],
|
147
|
+
return_type: "Elem",
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
class_rbi.create_method(
|
152
|
+
"find",
|
153
|
+
parameters: [Parameter.new("*args", type: "T.untyped")],
|
154
|
+
return_type: "Elem",
|
155
|
+
)
|
156
|
+
|
157
|
+
if Rails.version =~ /^5\.0/
|
158
|
+
item_methods = %w(first second third third_to_last second_to_last last)
|
159
|
+
item_methods.each do |item_method|
|
160
|
+
class_rbi.create_method(
|
161
|
+
item_method,
|
162
|
+
parameters: [Parameter.new("*args", type: "T.untyped")],
|
163
|
+
return_type: "T.nilable(Elem)",
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
boolean_methods = %w(any? many?)
|
168
|
+
boolean_methods.each do |boolean_method|
|
169
|
+
class_rbi.create_method(boolean_method, return_type: "T::Boolean")
|
170
|
+
end
|
171
|
+
else
|
172
|
+
class_rbi.create_method(
|
173
|
+
"last",
|
174
|
+
parameters: [Parameter.new("limit", type: "T.untyped", default: "nil")],
|
175
|
+
return_type: "T.nilable(Elem)",
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
if Rails.version =~ /^5\.(0|1)/
|
180
|
+
class_rbi.create_method("to_a", return_type: "T::Array[Elem]")
|
181
|
+
end
|
182
|
+
|
183
|
+
class_rbi.create_method('empty?', return_type: "T::Boolean")
|
184
|
+
end
|
185
|
+
|
186
|
+
parlour.rbi
|
187
|
+
end
|
188
|
+
|
189
|
+
sig {
|
190
|
+
params(
|
191
|
+
class_rbi: Parlour::RbiGenerator::Namespace,
|
192
|
+
type: String,
|
193
|
+
class_method: T::Boolean,
|
194
|
+
).void
|
195
|
+
}
|
196
|
+
def create_elem_specific_query_methods(class_rbi, type:, class_method:)
|
197
|
+
finder_methods = %w(find find_by find_by!)
|
198
|
+
finder_methods.each do |finder_method|
|
199
|
+
class_rbi.create_method(
|
200
|
+
finder_method,
|
201
|
+
parameters: [ Parameter.new("*args", type: "T.untyped") ],
|
202
|
+
return_type: (finder_method == 'find' || finder_method.ends_with?('!')) ? type : "T.nilable(#{type})",
|
203
|
+
class_method: class_method,
|
204
|
+
)
|
205
|
+
end
|
206
|
+
|
207
|
+
first_or_something_by_methods = %w(find_or_initialize_by find_or_create_by find_or_create_by!)
|
208
|
+
first_or_something_by_methods.each do |first_or_something_by_method|
|
209
|
+
class_rbi.create_method(
|
210
|
+
first_or_something_by_method,
|
211
|
+
parameters: [
|
212
|
+
Parameter.new("attributes", type: "T.untyped"),
|
213
|
+
Parameter.new(
|
214
|
+
"&block",
|
215
|
+
type: "T.nilable(T.proc.params(object: #{type}).void)",
|
216
|
+
),
|
217
|
+
],
|
218
|
+
return_type: type,
|
219
|
+
class_method: class_method
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
item_methods = %w(first first! second second! third third! third_to_last third_to_last! second_to_last second_to_last! last last!)
|
224
|
+
item_methods.each do |item_method|
|
225
|
+
class_rbi.create_method(
|
226
|
+
item_method,
|
227
|
+
return_type: item_method.ends_with?('!') ? type : "T.nilable(#{type})",
|
228
|
+
class_method: class_method,
|
229
|
+
)
|
230
|
+
end
|
231
|
+
|
232
|
+
build_methods = %w(create create! new build first_or_create first_or_create! first_or_initialize)
|
233
|
+
build_methods.each do |build_method|
|
234
|
+
# `build` method doesn't exist on the model, only on the relations
|
235
|
+
next if build_method == 'build' && class_method
|
236
|
+
|
237
|
+
# This needs to match the generated method signature in activerecord.rbi and
|
238
|
+
# in Rails 5.0 and 5.1 the param is a splat.
|
239
|
+
if Rails.version =~ /^5\.(0|1)/ && %w(new build create create!).include?(build_method)
|
240
|
+
param = Parameter.new("*args", type: "T.untyped")
|
241
|
+
else
|
242
|
+
param = Parameter.new("attributes", type: "T.untyped", default: 'nil')
|
243
|
+
end
|
244
|
+
|
245
|
+
class_rbi.create_method(
|
246
|
+
build_method,
|
247
|
+
parameters: [
|
248
|
+
param,
|
249
|
+
Parameter.new(
|
250
|
+
"&block",
|
251
|
+
type: "T.nilable(T.proc.params(object: #{type}).void)",
|
252
|
+
),
|
253
|
+
],
|
254
|
+
return_type: type,
|
255
|
+
class_method: class_method,
|
256
|
+
)
|
257
|
+
end
|
258
|
+
|
259
|
+
batch_methods = %w(find_each find_in_batches)
|
260
|
+
batch_methods.each do |batch_method|
|
261
|
+
inner_type = batch_method == 'find_each' ? type : "T::Array[#{type}]"
|
262
|
+
|
263
|
+
class_rbi.create_method(
|
264
|
+
batch_method,
|
265
|
+
parameters: [
|
266
|
+
Parameter.new("start:", type: "T.nilable(Integer)", default: "nil"),
|
267
|
+
Parameter.new("finish:", type: "T.nilable(Integer)", default: "nil"),
|
268
|
+
Parameter.new("batch_size:", type: "T.nilable(Integer)", default: "1000"),
|
269
|
+
Parameter.new("error_on_ignore:", type: "T.nilable(T::Boolean)", default: "nil"),
|
270
|
+
Parameter.new("&block", type: "T.nilable(T.proc.params(e: #{inner_type}).void)"),
|
271
|
+
],
|
272
|
+
return_type: "T::Enumerator[#{inner_type}]",
|
273
|
+
class_method: class_method,
|
274
|
+
)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
sig {
|
279
|
+
params(
|
280
|
+
class_rbi: Parlour::RbiGenerator::Namespace,
|
281
|
+
class_method: T::Boolean,
|
282
|
+
).void
|
283
|
+
}
|
284
|
+
def create_general_query_methods(class_rbi, class_method:)
|
285
|
+
class_rbi.create_method(
|
286
|
+
"exists?",
|
287
|
+
parameters: [ Parameter.new("conditions", type: "T.untyped", default: "nil") ],
|
288
|
+
return_type: "T::Boolean",
|
289
|
+
class_method: class_method,
|
290
|
+
)
|
291
|
+
|
292
|
+
boolean_methods = %w(any? many? none? one?)
|
293
|
+
boolean_methods.each do |boolean_method|
|
294
|
+
class_rbi.create_method(
|
295
|
+
boolean_method,
|
296
|
+
return_type: "T::Boolean",
|
297
|
+
class_method: class_method,
|
298
|
+
)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
data/lib/sorbet-rails/config.rb
CHANGED
@@ -28,3 +28,14 @@ end
|
|
28
28
|
class ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter;
|
29
29
|
def klass; end
|
30
30
|
end
|
31
|
+
|
32
|
+
class ActiveModel::Validations::PresenceValidator
|
33
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
34
|
+
attr_reader :options
|
35
|
+
end
|
36
|
+
|
37
|
+
module ActiveModel::Validations
|
38
|
+
module ClassMethods
|
39
|
+
def validators_on(*attributes); end
|
40
|
+
end
|
41
|
+
end
|
@@ -7,7 +7,6 @@ class ActiveFlagPlugin < SorbetRails::ModelPlugins::Base
|
|
7
7
|
|
8
8
|
module_name = self.model_module_name("GeneratedActiveFlagMethods")
|
9
9
|
module_rbi = root.create_module(module_name)
|
10
|
-
module_rbi.create_extend("T::Sig")
|
11
10
|
|
12
11
|
model_class_rbi = root.create_class(self.model_class_name)
|
13
12
|
model_class_rbi.create_include(module_name)
|
@@ -8,7 +8,6 @@ class PaperclipPlugin < SorbetRails::ModelPlugins::Base
|
|
8
8
|
|
9
9
|
module_name = self.model_module_name("GeneratedPaperclipMethods")
|
10
10
|
module_rbi = root.create_module(module_name)
|
11
|
-
module_rbi.create_extend("T::Sig")
|
12
11
|
|
13
12
|
model_class_rbi = root.create_class(self.model_class_name)
|
14
13
|
model_class_rbi.create_include(module_name)
|
@@ -13,7 +13,6 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
13
13
|
|
14
14
|
assoc_module_name = self.model_module_name("GeneratedAssociationMethods")
|
15
15
|
assoc_module_rbi = root.create_module(assoc_module_name)
|
16
|
-
assoc_module_rbi.create_extend("T::Sig")
|
17
16
|
|
18
17
|
model_class_rbi = root.create_class(self.model_class_name)
|
19
18
|
model_class_rbi.create_include(assoc_module_name)
|
@@ -35,7 +34,7 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
35
34
|
def populate_single_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection)
|
36
35
|
# TODO allow people to specify the possible values of polymorphic associations
|
37
36
|
assoc_class = assoc_should_be_untyped?(reflection) ? "T.untyped" : "::#{reflection.klass.name}"
|
38
|
-
assoc_type = belongs_to_and_required?(reflection) ? assoc_class : "T.nilable(#{assoc_class})"
|
37
|
+
assoc_type = (belongs_to_and_required?(reflection) || has_one_and_required?(reflection)) ? assoc_class : "T.nilable(#{assoc_class})"
|
39
38
|
|
40
39
|
assoc_module_rbi.create_method(
|
41
40
|
assoc_name.to_s,
|
@@ -56,6 +55,9 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
56
55
|
# optional (via `optional` or `!required` or `!belongs_to_required_by_default`)
|
57
56
|
return false if !reflection.belongs_to?
|
58
57
|
|
58
|
+
column_def = @columns_hash[reflection.foreign_key.to_s]
|
59
|
+
db_required_config = column_def.present? && !column_def.null
|
60
|
+
|
59
61
|
rails_required_config =
|
60
62
|
if reflection.options.key?(:required)
|
61
63
|
!!reflection.options[:required]
|
@@ -65,22 +67,34 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
65
67
|
!!reflection.active_record.belongs_to_required_by_default
|
66
68
|
end
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
+
# We check for validations on both the column name (e.g. wizard_id) and
|
71
|
+
# association name (e.g. wizard).
|
72
|
+
rails_required_config ||= [column_def&.name, reflection.name].compact.any? { |n| attribute_has_unconditional_presence_validation?(n) }
|
70
73
|
|
71
74
|
if rails_required_config && !db_required_config
|
72
75
|
puts "Warning: belongs_to association #{reflection.name} is required at the application
|
73
76
|
level but **nullable** at the DB level.\n Add a constraint at the DB level
|
74
77
|
(using `null: false` and foreign key constraint) to ensure it is enforced.".squish!
|
75
78
|
elsif !rails_required_config && db_required_config
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
+
if habtm_class?
|
80
|
+
puts "Note: belongs_to association #{reflection.name} is specified as not-null at the
|
81
|
+
DB level but will always be **optional** at the application level since it's part of a
|
82
|
+
has_and_belongs_to_many association.\n To resolve move to a 'has_many through:' association.".squish!
|
83
|
+
else
|
84
|
+
puts "Note: belongs_to association #{reflection.name} is specified as not-null at the
|
85
|
+
DB level but **optional** at the application level.\n Add a constraint at the app level
|
86
|
+
(using `optional: false`) as a validation hint to Rails.".squish!
|
87
|
+
end
|
79
88
|
end
|
80
89
|
|
81
90
|
rails_required_config || db_required_config
|
82
91
|
end
|
83
92
|
|
93
|
+
sig { params(reflection: T.untyped).returns(T::Boolean) }
|
94
|
+
private def has_one_and_required?(reflection)
|
95
|
+
!!(reflection.has_one? && attribute_has_unconditional_presence_validation?(reflection.name))
|
96
|
+
end
|
97
|
+
|
84
98
|
sig do
|
85
99
|
params(
|
86
100
|
assoc_module_rbi: T.untyped,
|
@@ -99,6 +113,12 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
99
113
|
assoc_name.to_s,
|
100
114
|
return_type: relation_class,
|
101
115
|
)
|
116
|
+
unless assoc_should_be_untyped?(reflection)
|
117
|
+
assoc_module_rbi.create_method(
|
118
|
+
"#{assoc_name.singularize}_ids",
|
119
|
+
return_type: "T::Array[Integer]",
|
120
|
+
)
|
121
|
+
end
|
102
122
|
assoc_module_rbi.create_method(
|
103
123
|
"#{assoc_name}=",
|
104
124
|
parameters: [
|
@@ -110,7 +130,15 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
110
130
|
|
111
131
|
sig { params(reflection: T.untyped).returns(T.nilable(T::Boolean)) }
|
112
132
|
def assoc_should_be_untyped?(reflection)
|
113
|
-
|
133
|
+
# For some polymorphic associations (e.g. a has-many-through where the `source`
|
134
|
+
# is polymorphic) we can figure out the type from the class_name or source_type.
|
135
|
+
polymorpic_with_unknowable_klass = (
|
136
|
+
polymorphic_assoc?(reflection) &&
|
137
|
+
!reflection.options.key?(:class_name) &&
|
138
|
+
!reflection.options.key?(:source_type)
|
139
|
+
)
|
140
|
+
|
141
|
+
polymorpic_with_unknowable_klass || !@available_classes.include?(reflection.klass.name)
|
114
142
|
end
|
115
143
|
|
116
144
|
sig { params(reflection: T.untyped).returns(T.nilable(T::Boolean)) }
|