sorbet-rails 0.5.8.1 → 0.5.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/README.md +27 -4
- data/lib/bundled_rbi/active_record_relation.rbi +79 -0
- data/lib/sorbet-rails/gem_plugins/active_flag_plugin.rb +47 -0
- data/lib/sorbet-rails/gem_plugins/kaminari_plugin.rb +20 -1
- data/lib/sorbet-rails/gem_plugins/paperclip_plugin.rb +33 -0
- data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +22 -6
- data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +10 -2
- data/lib/sorbet-rails/model_plugins/active_record_finder_methods.rb +46 -35
- data/lib/sorbet-rails/model_plugins/enumerable_collections.rb +40 -34
- data/lib/sorbet-rails/model_plugins/plugins.rb +6 -0
- data/lib/sorbet-rails/routes_rbi_formatter.rb +4 -0
- data/lib/sorbet-rails/tasks/rails_rbi.rake +2 -1
- data/sorbet-rails.gemspec +2 -2
- data/spec/generators/rails-template.rb +25 -3
- data/spec/generators/sorbet_test_cases.rb +5 -0
- data/spec/model_rbi_formatter_spec.rb +1 -1
- data/spec/rake_rails_rbi_models_spec.rb +1 -0
- data/spec/support/v5.0/Gemfile +1 -1
- data/spec/support/v5.0/Gemfile.lock +18 -14
- data/spec/support/v5.0/app/models/school.rb +3 -0
- data/spec/support/v5.0/app/models/spell_book.rb +3 -1
- data/spec/support/v5.0/app/models/wizard.rb +3 -0
- data/spec/support/v5.0/config/environments/development.rb +1 -1
- data/spec/support/v5.0/config/initializers/sorbet_rails.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000003_create_spell_books.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000008_add_robe_to_wizard.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000009_add_school.rb +10 -0
- data/spec/support/v5.0/db/schema.rb +9 -4
- data/spec/support/v5.0/sorbet_test_cases.rb +5 -0
- data/spec/support/v5.1/Gemfile +1 -1
- data/spec/support/v5.1/Gemfile.lock +18 -14
- data/spec/support/v5.1/app/models/school.rb +3 -0
- data/spec/support/v5.1/app/models/spell_book.rb +3 -1
- data/spec/support/v5.1/app/models/wizard.rb +3 -0
- data/spec/support/v5.1/config/environments/test.rb +1 -1
- data/spec/support/v5.1/config/initializers/sorbet_rails.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000003_create_spell_books.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000008_add_robe_to_wizard.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000009_add_school.rb +10 -0
- data/spec/support/v5.1/db/schema.rb +9 -4
- data/spec/support/v5.1/sorbet_test_cases.rb +5 -0
- data/spec/support/v5.2/Gemfile +1 -1
- data/spec/support/v5.2/Gemfile.lock +54 -50
- data/spec/support/v5.2/app/models/school.rb +3 -0
- data/spec/support/v5.2/app/models/spell_book.rb +3 -1
- data/spec/support/v5.2/app/models/wizard.rb +3 -0
- data/spec/support/v5.2/config/environments/development.rb +1 -1
- data/spec/support/v5.2/config/initializers/sorbet_rails.rb +1 -1
- data/spec/support/v5.2/db/migrate/20190620000003_create_spell_books.rb +1 -1
- data/spec/support/v5.2/db/migrate/20190620000008_add_robe_to_wizard.rb +1 -1
- data/spec/support/v5.2/db/migrate/20190620000009_add_school.rb +10 -0
- data/spec/support/v5.2/db/schema.rb +9 -4
- data/spec/support/v5.2/sorbet_test_cases.rb +5 -0
- data/spec/support/v6.0/Gemfile +1 -1
- data/spec/support/v6.0/Gemfile.lock +70 -66
- data/spec/support/v6.0/app/models/school.rb +3 -0
- data/spec/support/v6.0/app/models/spell_book.rb +3 -1
- data/spec/support/v6.0/app/models/wizard.rb +3 -0
- data/spec/support/v6.0/config/environments/development.rb +1 -1
- data/spec/support/v6.0/config/initializers/sorbet_rails.rb +1 -1
- data/spec/support/v6.0/db/migrate/20190620000003_create_spell_books.rb +1 -1
- data/spec/support/v6.0/db/migrate/20190620000008_add_robe_to_wizard.rb +1 -1
- data/spec/support/v6.0/db/migrate/20190620000009_add_school.rb +10 -0
- data/spec/support/v6.0/db/schema.rb +10 -4
- data/spec/support/v6.0/sorbet_test_cases.rb +5 -0
- data/spec/test_data/v5.0/expected_internal_metadata.rbi +0 -77
- data/spec/test_data/v5.0/expected_potion.rbi +0 -77
- data/spec/test_data/v5.0/expected_robe.rbi +23 -77
- data/spec/test_data/v5.0/expected_routes.rbi +4 -0
- data/spec/test_data/v5.0/expected_schema_migration.rbi +0 -77
- data/spec/test_data/v5.0/expected_school.rbi +651 -0
- data/spec/test_data/v5.0/expected_spell_book.rbi +3 -80
- data/spec/test_data/v5.0/expected_squib.rbi +17 -79
- data/spec/test_data/v5.0/expected_wand.rbi +4 -81
- data/spec/test_data/v5.0/expected_wizard.rbi +17 -79
- data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +17 -79
- data/spec/test_data/v5.1/expected_internal_metadata.rbi +0 -77
- data/spec/test_data/v5.1/expected_potion.rbi +0 -77
- data/spec/test_data/v5.1/expected_robe.rbi +23 -77
- data/spec/test_data/v5.1/expected_routes.rbi +4 -0
- data/spec/test_data/v5.1/expected_schema_migration.rbi +0 -77
- data/spec/test_data/v5.1/expected_school.rbi +663 -0
- data/spec/test_data/v5.1/expected_spell_book.rbi +3 -80
- data/spec/test_data/v5.1/expected_squib.rbi +17 -79
- data/spec/test_data/v5.1/expected_wand.rbi +4 -81
- data/spec/test_data/v5.1/expected_wizard.rbi +17 -79
- data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +17 -79
- data/spec/test_data/v5.2/expected_attachment.rbi +0 -77
- data/spec/test_data/v5.2/expected_blob.rbi +0 -77
- data/spec/test_data/v5.2/expected_internal_metadata.rbi +0 -77
- data/spec/test_data/v5.2/expected_potion.rbi +0 -77
- data/spec/test_data/v5.2/expected_robe.rbi +23 -77
- data/spec/test_data/v5.2/expected_routes.rbi +4 -0
- data/spec/test_data/v5.2/expected_schema_migration.rbi +0 -77
- data/spec/test_data/v5.2/expected_school.rbi +663 -0
- data/spec/test_data/v5.2/expected_spell_book.rbi +3 -80
- data/spec/test_data/v5.2/expected_squib.rbi +17 -79
- data/spec/test_data/v5.2/expected_wand.rbi +4 -81
- data/spec/test_data/v5.2/expected_wizard.rbi +17 -79
- data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +17 -79
- data/spec/test_data/v6.0/expected_attachment.rbi +0 -77
- data/spec/test_data/v6.0/expected_blob.rbi +0 -77
- data/spec/test_data/v6.0/expected_internal_metadata.rbi +0 -77
- data/spec/test_data/v6.0/expected_potion.rbi +0 -77
- data/spec/test_data/v6.0/expected_robe.rbi +23 -77
- data/spec/test_data/v6.0/expected_routes.rbi +4 -0
- data/spec/test_data/v6.0/expected_schema_migration.rbi +0 -77
- data/spec/test_data/v6.0/expected_school.rbi +711 -0
- data/spec/test_data/v6.0/expected_spell_book.rbi +3 -80
- data/spec/test_data/v6.0/expected_squib.rbi +17 -79
- data/spec/test_data/v6.0/expected_wand.rbi +4 -81
- data/spec/test_data/v6.0/expected_wizard.rbi +17 -79
- data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +17 -79
- metadata +31 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bebc32701fb31995a1cf7f25ae7c2c7a9088347e8665dad3f182474483f03cc8
|
4
|
+
data.tar.gz: 8ae391dbadcc26831eebfef0969834969fe7c77ca8dd203f9ef05a7be26c19e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79759ed27498ebdced14582be0acfd6eef70c088a0eacf31335c9043cd43c22f5dc3b7f7638ea2c5ca1ba73e7209bc5cea3780abcae7ba427572050ee7414c39
|
7
|
+
data.tar.gz: a99f4c32fd69d120cbad6efc954c11fa8ad9cfbc6adb4c7e6e72422129984e25904134d725dfc8aec96b47dab9193085352a4fd55858124a5906ae41c8b42498
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -19,16 +19,15 @@ This gem adds a few Rake tasks to generate Ruby Interface (RBI) files for dynami
|
|
19
19
|
|
20
20
|
```
|
21
21
|
# -- Gemfile --
|
22
|
-
|
23
22
|
gem 'sorbet-rails'
|
24
23
|
```
|
25
24
|
|
25
|
+
Add `sorbet-rails` to the [`:default` group](https://bundler.io/v2.0/guides/groups.html) because of "Features Provided at Runtime" below.
|
26
|
+
|
26
27
|
```sh
|
27
28
|
❯ bundle install
|
28
29
|
```
|
29
30
|
|
30
|
-
Warning: *don't* add `sorbet-rails` to a specific environment group (eg. `development` only). `sorbet-rails` adds a bunch of helper methods to your Rails runtime as well as generators for RBI files. You'll want to run the gem in all environments.
|
31
|
-
|
32
31
|
3. Generate RBI files for your routes, models, etc
|
33
32
|
```sh
|
34
33
|
❯ rake rails_rbi:routes
|
@@ -48,7 +47,7 @@ Warning: *don't* add `sorbet-rails` to a specific environment group (eg. `develo
|
|
48
47
|
```
|
49
48
|
Because we've generated RBI files for routes, models, and helpers, a lot more files should be typecheckable now. Many methods in `hidden.rbi` may be removed because they are now typed.
|
50
49
|
|
51
|
-
##
|
50
|
+
## Static RBI Generation
|
52
51
|
|
53
52
|
### Models
|
54
53
|
|
@@ -148,6 +147,26 @@ Since mailing action methods is based on instance methods defined in a mailer cl
|
|
148
147
|
- If not, all the params in the mailing action method will be T.untyped.
|
149
148
|
- For return type though, the mailing action method will return `ActionMailer::MessageDelivery` instead of the return type of the instance method.
|
150
149
|
|
150
|
+
## Runtime Features
|
151
|
+
|
152
|
+
In addition to features provided by the static generator, `sorbet-rails` can
|
153
|
+
provide additional features when `require`d. This is why the installation
|
154
|
+
instructions specify that `sorbet-rails` should be placed in the [`:default`
|
155
|
+
group](https://bundler.io/v2.0/guides/groups.html) of the `Gemfile`, not a
|
156
|
+
specific environment group (eg. `development` only).
|
157
|
+
|
158
|
+
- Relation class: Making the relations available at runtime (they are normally private constants, the gem makes them public)
|
159
|
+
- Examples: `User::ActiveRecord_Relation`, `User::ActiveRecord_AssociationRelation`
|
160
|
+
- Model: `find_n`, `first_n`, `last_n` are the methods we added. `pluck_to_tstruct` as well.
|
161
|
+
- Controller: adding `fetch_typed` and `require_typed`
|
162
|
+
|
163
|
+
In addition to `require`ing `sorbet-rails`, you must also run
|
164
|
+
`rake rails_rbi:custom`, which will produce the RBI for these runtime features.
|
165
|
+
|
166
|
+
Discussion:
|
167
|
+
[#211](https://github.com/chanzuckerberg/sorbet-rails/issues/211),
|
168
|
+
[#214](https://github.com/chanzuckerberg/sorbet-rails/pull/214#issuecomment-546505485)
|
169
|
+
|
151
170
|
## Tips & Tricks
|
152
171
|
|
153
172
|
### Overriding generated signatures
|
@@ -342,6 +361,8 @@ These are the currently-supported gems and their symbolized names:
|
|
342
361
|
| [Kaminari] | `:kaminari` |
|
343
362
|
| [PgSearch] | `:pg_search` |
|
344
363
|
| [Shrine] | `:shrine` |
|
364
|
+
| [active_flag]| `:active_flag` |
|
365
|
+
| [Paperclip] | `:paperclip` |
|
345
366
|
|
346
367
|
You can also configure the core model plugins if needed. The default plugins are defined in the [config](https://github.com/chanzuckerberg/sorbet-rails/blob/master/lib/sorbet-rails/config.rb). For the full list of plugin symbols, check out [here](https://github.com/chanzuckerberg/sorbet-rails/blob/master/lib/sorbet-rails/model_plugins/plugins.rb).
|
347
368
|
|
@@ -351,6 +372,8 @@ You can also configure the core model plugins if needed. The default plugins are
|
|
351
372
|
[FriendlyId]: https://github.com/norman/friendly_id
|
352
373
|
[ElasticSearch]: https://github.com/elastic/elasticsearch-rails
|
353
374
|
[Shrine]: https://github.com/shrinerb/shrine
|
375
|
+
[active_flag]: https://github.com/kenn/active_flag
|
376
|
+
[Paperclip]: https://github.com/thoughtbot/paperclip
|
354
377
|
|
355
378
|
## Contributing
|
356
379
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# typed: strong
|
2
|
+
class ActiveRecord::Relation
|
3
|
+
sig { params(args: T.untyped).returns(Elem) }
|
4
|
+
def find(*args); end
|
5
|
+
|
6
|
+
sig { params(args: T.untyped).returns(T.nilable(Elem)) }
|
7
|
+
def find_by(*args); end
|
8
|
+
|
9
|
+
sig { params(args: T.untyped).returns(Elem) }
|
10
|
+
def find_by!(*args); end
|
11
|
+
|
12
|
+
sig { returns(T.nilable(Elem)) }
|
13
|
+
def first; end
|
14
|
+
|
15
|
+
sig { returns(Elem) }
|
16
|
+
def first!; end
|
17
|
+
|
18
|
+
sig { returns(T.nilable(Elem)) }
|
19
|
+
def second; end
|
20
|
+
|
21
|
+
sig { returns(Elem) }
|
22
|
+
def second!; end
|
23
|
+
|
24
|
+
sig { returns(T.nilable(Elem)) }
|
25
|
+
def third; end
|
26
|
+
|
27
|
+
sig { returns(Elem) }
|
28
|
+
def third!; end
|
29
|
+
|
30
|
+
sig { returns(T.nilable(Elem)) }
|
31
|
+
def third_to_last; end
|
32
|
+
|
33
|
+
sig { returns(Elem) }
|
34
|
+
def third_to_last!; end
|
35
|
+
|
36
|
+
sig { returns(T.nilable(Elem)) }
|
37
|
+
def second_to_last; end
|
38
|
+
|
39
|
+
sig { returns(Elem) }
|
40
|
+
def second_to_last!; end
|
41
|
+
|
42
|
+
sig { returns(T.nilable(Elem)) }
|
43
|
+
def last; end
|
44
|
+
|
45
|
+
sig { returns(Elem) }
|
46
|
+
def last!; end
|
47
|
+
|
48
|
+
sig { override.params(block: T.proc.params(e: Elem).void).returns(T::Array[Elem]) }
|
49
|
+
def each(&block); end
|
50
|
+
|
51
|
+
sig { params(level: T.nilable(Integer)).returns(T::Array[Elem]) }
|
52
|
+
def flatten(level); end
|
53
|
+
|
54
|
+
sig { returns(T::Array[Elem]) }
|
55
|
+
def to_a; end
|
56
|
+
|
57
|
+
sig do
|
58
|
+
type_parameters(:U).params(
|
59
|
+
blk: T.proc.params(arg0: Elem).returns(T.type_parameter(:U)),
|
60
|
+
)
|
61
|
+
.returns(T::Array[T.type_parameter(:U)])
|
62
|
+
end
|
63
|
+
def map(&blk); end
|
64
|
+
|
65
|
+
sig { params(conditions: T.untyped).returns(T::Boolean) }
|
66
|
+
def exists?(conditions = nil); end
|
67
|
+
|
68
|
+
sig { returns(T::Boolean) }
|
69
|
+
def any?; end
|
70
|
+
|
71
|
+
sig { returns(T::Boolean) }
|
72
|
+
def many?; end
|
73
|
+
|
74
|
+
sig { returns(T::Boolean) }
|
75
|
+
def none?; end
|
76
|
+
|
77
|
+
sig { returns(T::Boolean) }
|
78
|
+
def one?; end
|
79
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# typed: strict
|
2
|
+
class ActiveFlagPlugin < SorbetRails::ModelPlugins::Base
|
3
|
+
sig { params(root: Parlour::RbiGenerator::Namespace).void.override }
|
4
|
+
def generate(root)
|
5
|
+
# method added here: https://github.com/kenn/active_flag/blob/master/lib/active_flag.rb#L13
|
6
|
+
return unless model_class.respond_to?(:active_flags)
|
7
|
+
|
8
|
+
module_name = self.model_module_name("GeneratedActiveFlagMethods")
|
9
|
+
module_rbi = root.create_module(module_name)
|
10
|
+
module_rbi.create_extend("T::Sig")
|
11
|
+
|
12
|
+
model_class_rbi = root.create_class(self.model_class_name)
|
13
|
+
model_class_rbi.create_include(module_name)
|
14
|
+
|
15
|
+
module_rbi.create_method(
|
16
|
+
"active_flags",
|
17
|
+
return_type: "T::Hash[Symbol, ActiveFlag::Definition]",
|
18
|
+
class_method: true
|
19
|
+
)
|
20
|
+
|
21
|
+
active_flag_keys(model_class).each do |flag|
|
22
|
+
module_rbi.create_method(
|
23
|
+
flag.to_s,
|
24
|
+
return_type: "::ActiveFlag::Value"
|
25
|
+
)
|
26
|
+
|
27
|
+
module_rbi.create_method(
|
28
|
+
"#{flag}=",
|
29
|
+
parameters: [
|
30
|
+
Parameter.new("value", type: "T::Array[Symbol]")
|
31
|
+
],
|
32
|
+
return_type: nil
|
33
|
+
)
|
34
|
+
|
35
|
+
module_rbi.create_method(
|
36
|
+
flag.to_s.pluralize,
|
37
|
+
return_type: "::ActiveFlag::Definition",
|
38
|
+
class_method: true
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { params(model_class: T.class_of(ActiveRecord::Base)).returns(T::Array[Symbol]) }
|
44
|
+
def active_flag_keys(model_class)
|
45
|
+
T.unsafe(model_class).active_flags.keys
|
46
|
+
end
|
47
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
class KaminariPlugin < SorbetRails::ModelPlugins::Base
|
3
3
|
# Kaminari generates a dynamic `page` method on ActiveRecord relations.
|
4
|
-
# https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-activerecord/lib/kaminari/activerecord/active_record_model_extension.rb#L15
|
5
4
|
sig { override.params(root: Parlour::RbiGenerator::Namespace).void }
|
6
5
|
def generate(root)
|
7
6
|
return unless @model_class.include?(::Kaminari::ActiveRecordModelExtension)
|
@@ -9,6 +8,7 @@ class KaminariPlugin < SorbetRails::ModelPlugins::Base
|
|
9
8
|
# Get the configured Kaminari page method name, or fall back to 'page' if necessary.
|
10
9
|
page_method = T.unsafe(::Kaminari).config.page_method_name || 'page'
|
11
10
|
|
11
|
+
# https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-activerecord/lib/kaminari/activerecord/active_record_model_extension.rb#L15
|
12
12
|
add_relation_query_method(
|
13
13
|
root,
|
14
14
|
page_method.to_s,
|
@@ -16,5 +16,24 @@ class KaminariPlugin < SorbetRails::ModelPlugins::Base
|
|
16
16
|
Parameter.new('num', type: 'T.nilable(Integer)', default: 'nil')
|
17
17
|
],
|
18
18
|
)
|
19
|
+
|
20
|
+
# https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-core/lib/kaminari/models/page_scope_methods.rb#L7
|
21
|
+
add_relation_query_method(
|
22
|
+
root,
|
23
|
+
"per",
|
24
|
+
parameters: [
|
25
|
+
Parameter.new('num', type: 'Integer'),
|
26
|
+
Parameter.new('max_per_page', type: 'T.nilable(Integer)', default: 'nil')
|
27
|
+
],
|
28
|
+
)
|
29
|
+
|
30
|
+
# https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-core/lib/kaminari/models/page_scope_methods.rb#L27
|
31
|
+
add_relation_query_method(
|
32
|
+
root,
|
33
|
+
"padding",
|
34
|
+
parameters: [
|
35
|
+
Parameter.new('num', type: 'Integer')
|
36
|
+
],
|
37
|
+
)
|
19
38
|
end
|
20
39
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# typed: strict
|
2
|
+
class PaperclipPlugin < SorbetRails::ModelPlugins::Base
|
3
|
+
sig { params(root: Parlour::RbiGenerator::Namespace).void.override }
|
4
|
+
def generate(root)
|
5
|
+
# method added here: https://github.com/thoughtbot/paperclip/blob/v5.2.1/lib/paperclip/has_attached_file.rb#L110
|
6
|
+
|
7
|
+
return unless model_class.respond_to?(:attachment_definitions)
|
8
|
+
|
9
|
+
module_name = self.model_module_name("GeneratedPaperclipMethods")
|
10
|
+
module_rbi = root.create_module(module_name)
|
11
|
+
module_rbi.create_extend("T::Sig")
|
12
|
+
|
13
|
+
model_class_rbi = root.create_class(self.model_class_name)
|
14
|
+
model_class_rbi.create_include(module_name)
|
15
|
+
|
16
|
+
T.unsafe(::Paperclip::AttachmentRegistry).names_for(model_class).each do |attachment|
|
17
|
+
# https://github.com/thoughtbot/paperclip/blob/v5.2.1/lib/paperclip/has_attached_file.rb#L42
|
18
|
+
module_rbi.create_method(
|
19
|
+
attachment.to_s,
|
20
|
+
return_type: "::Paperclip::Attachment"
|
21
|
+
)
|
22
|
+
|
23
|
+
# https://github.com/thoughtbot/paperclip/blob/v5.2.1/lib/paperclip/attachment.rb#L100
|
24
|
+
module_rbi.create_method(
|
25
|
+
"#{attachment}=",
|
26
|
+
parameters: [
|
27
|
+
Parameter.new("uploaded_file", type: "T.untyped") # could be a variety of things
|
28
|
+
],
|
29
|
+
return_type: nil
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -56,13 +56,29 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
56
56
|
# optional (via `optional` or `!required` or `!belongs_to_required_by_default`)
|
57
57
|
return false if !reflection.belongs_to?
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
59
|
+
rails_required_config =
|
60
|
+
if reflection.options.key?(:required)
|
61
|
+
!!reflection.options[:required]
|
62
|
+
elsif reflection.options.key?(:optional)
|
63
|
+
!reflection.options[:optional]
|
64
|
+
else
|
65
|
+
!!reflection.active_record.belongs_to_required_by_default
|
66
|
+
end
|
67
|
+
|
68
|
+
column_def = @columns_hash[reflection.foreign_key.to_s]
|
69
|
+
db_required_config = column_def && !column_def.null
|
70
|
+
|
71
|
+
if rails_required_config && !db_required_config
|
72
|
+
puts "Warning: belongs_to association #{reflection.name} is required at the application
|
73
|
+
level but **nullable** at the DB level. Add a constraint at the DB level
|
74
|
+
(using `null: false` and foreign key constraint) to ensure it is enforced.".squish!
|
75
|
+
elsif !rails_required_config && db_required_config
|
76
|
+
puts "Warning: belongs_to association #{reflection.name} is specified as not-null at the
|
77
|
+
DB level but **not required** at the application level. Add a constraint at the app level
|
78
|
+
(using `optional: false`) as a validation hint to Rails.".squish!
|
65
79
|
end
|
80
|
+
|
81
|
+
rails_required_config || db_required_config
|
66
82
|
end
|
67
83
|
|
68
84
|
sig do
|
@@ -131,15 +131,23 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
|
|
131
131
|
|
132
132
|
sig { params(column_type: Object).returns(String) }
|
133
133
|
def value_type_for_attr_writer(column_type)
|
134
|
-
assignable_time_types = [DateTime, Date, Time, ActiveSupport::TimeWithZone].map(&:to_s)
|
135
|
-
|
136
134
|
# it's safe - and convenient - to assign any "time like" object to a time zone
|
137
135
|
# aware attribute because Rails will cast it to a `ActiveSupport::TimeWithZone`
|
138
136
|
# (so rereading the attribute will always return the `TimeWithZone` type)
|
137
|
+
assignable_time_types = [DateTime, Date, Time, ActiveSupport::TimeWithZone].map(&:to_s)
|
138
|
+
|
139
|
+
# same thing applies with the many types that respond to `#to_i` or `#to_f`
|
140
|
+
assignable_numeric_types = [Integer, Float, ActiveSupport::Duration]
|
141
|
+
|
142
|
+
# TODO: this could be a lot tidier
|
139
143
|
if column_type == ActiveSupport::TimeWithZone
|
140
144
|
"T.any(#{assignable_time_types.join(', ')})"
|
141
145
|
elsif column_type == "T.nilable(ActiveSupport::TimeWithZone)"
|
142
146
|
"T.nilable(T.any(#{assignable_time_types.join(', ')}))"
|
147
|
+
elsif ["T.nilable(Float)", "T.nilable(Integer)"].include?(column_type)
|
148
|
+
"T.nilable(T.any(#{assignable_numeric_types.join(', ')}))"
|
149
|
+
elsif ["Float", "Integer"].include?(column_type.to_s)
|
150
|
+
"T.any(#{assignable_numeric_types.join(', ')})"
|
143
151
|
elsif column_type == String
|
144
152
|
'T.any(String, Symbol)'
|
145
153
|
else
|
@@ -8,7 +8,7 @@ class SorbetRails::ModelPlugins::ActiveRecordFinderMethods < SorbetRails::ModelP
|
|
8
8
|
create_finder_methods_for(model_class_rbi, class_method: true)
|
9
9
|
|
10
10
|
model_relation_class_rbi = root.create_class(self.model_relation_class_name)
|
11
|
-
create_finder_methods_for(model_relation_class_rbi, class_method: false)
|
11
|
+
create_finder_methods_for(model_relation_class_rbi, class_method: false, include_methods_that_return_self: false)
|
12
12
|
|
13
13
|
model_assoc_proxy_class_rbi = root.create_class(self.model_assoc_proxy_class_name)
|
14
14
|
create_finder_methods_for(model_assoc_proxy_class_rbi, class_method: false)
|
@@ -17,46 +17,57 @@ class SorbetRails::ModelPlugins::ActiveRecordFinderMethods < SorbetRails::ModelP
|
|
17
17
|
create_finder_methods_for(model_assoc_relation_rbi, class_method: false)
|
18
18
|
end
|
19
19
|
|
20
|
-
sig
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
20
|
+
sig do
|
21
|
+
params(
|
22
|
+
class_rbi: Parlour::RbiGenerator::ClassNamespace,
|
23
|
+
class_method: T::Boolean,
|
24
|
+
include_methods_that_return_self: T::Boolean
|
25
|
+
).void
|
26
|
+
end
|
27
|
+
def create_finder_methods_for(class_rbi, class_method:, include_methods_that_return_self: true)
|
28
|
+
# The intent of this is to prevent these methods from being generated
|
29
|
+
# unnecessarily when T.attached_class or Elem can be used to reduce
|
30
|
+
# the number of method signatures that have to be generated.
|
31
|
+
if include_methods_that_return_self
|
32
|
+
class_rbi.create_method(
|
33
|
+
"find",
|
34
|
+
parameters: [ Parameter.new("*args", type: "T.untyped") ],
|
35
|
+
return_type: self.model_class_name,
|
36
|
+
class_method: class_method,
|
37
|
+
)
|
38
|
+
class_rbi.create_method(
|
39
|
+
"find_by",
|
40
|
+
parameters: [ Parameter.new("*args", type: "T.untyped") ],
|
41
|
+
return_type: "T.nilable(#{self.model_class_name})",
|
42
|
+
class_method: class_method,
|
43
|
+
)
|
44
|
+
class_rbi.create_method(
|
45
|
+
"find_by!",
|
46
|
+
parameters: [ Parameter.new("*args", type: "T.untyped") ],
|
47
|
+
return_type: self.model_class_name,
|
48
|
+
class_method: class_method
|
49
|
+
)
|
40
50
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
51
|
+
["first", "second", "third", "third_to_last", "second_to_last", "last"].
|
52
|
+
each do |method_name|
|
53
|
+
create_finder_method_pair(class_rbi, method_name, class_method)
|
54
|
+
end
|
45
55
|
|
46
|
-
|
47
|
-
class_rbi.create_method(
|
48
|
-
"exists?",
|
49
|
-
parameters: [ Parameter.new("conditions", type: "T.untyped", default: "nil") ],
|
50
|
-
return_type: "T::Boolean",
|
51
|
-
class_method: class_method,
|
52
|
-
)
|
53
|
-
["any?", "many?", "none?", "one?"].each do |method_name|
|
56
|
+
# Checker methods
|
54
57
|
class_rbi.create_method(
|
55
|
-
|
56
|
-
parameters: [ Parameter.new("
|
58
|
+
"exists?",
|
59
|
+
parameters: [ Parameter.new("conditions", type: "T.untyped", default: "nil") ],
|
57
60
|
return_type: "T::Boolean",
|
58
61
|
class_method: class_method,
|
59
62
|
)
|
63
|
+
["any?", "many?", "none?", "one?"].each do |method_name|
|
64
|
+
class_rbi.create_method(
|
65
|
+
method_name,
|
66
|
+
parameters: [ Parameter.new("*args", type: "T.untyped") ],
|
67
|
+
return_type: "T::Boolean",
|
68
|
+
class_method: class_method,
|
69
|
+
)
|
70
|
+
end
|
60
71
|
end
|
61
72
|
end
|
62
73
|
|
@@ -4,10 +4,10 @@ class SorbetRails::ModelPlugins::EnumerableCollections < SorbetRails::ModelPlugi
|
|
4
4
|
|
5
5
|
sig { override.params(root: Parlour::RbiGenerator::Namespace).void }
|
6
6
|
def generate(root)
|
7
|
-
# model relation & association proxy are enumerable
|
7
|
+
# model relation, assocation relation, & association proxy are enumerable
|
8
8
|
# we need to implement "each" in these methods so that they work
|
9
9
|
model_relation_class_rbi = root.create_class(self.model_relation_class_name)
|
10
|
-
create_enumerable_methods_for(model_relation_class_rbi)
|
10
|
+
create_enumerable_methods_for(model_relation_class_rbi, include_methods: false)
|
11
11
|
|
12
12
|
model_assoc_relation_rbi = root.create_class(self.model_assoc_relation_class_name)
|
13
13
|
create_enumerable_methods_for(model_assoc_relation_rbi)
|
@@ -28,38 +28,44 @@ class SorbetRails::ModelPlugins::EnumerableCollections < SorbetRails::ModelPlugi
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
sig { params(class_rbi: Parlour::RbiGenerator::ClassNamespace).void }
|
32
|
-
def create_enumerable_methods_for(class_rbi)
|
31
|
+
sig { params(class_rbi: Parlour::RbiGenerator::ClassNamespace, include_methods: T::Boolean).void }
|
32
|
+
def create_enumerable_methods_for(class_rbi, include_methods: true)
|
33
33
|
class_rbi.create_include("Enumerable")
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
34
|
+
|
35
|
+
# An escape hatch that prevents these methods from being added unnecessarily.
|
36
|
+
# In certain cases, we can remove these from the classes because they can
|
37
|
+
# be generalized to use Elem in a superclass instead.
|
38
|
+
if include_methods
|
39
|
+
class_rbi.create_method(
|
40
|
+
"each",
|
41
|
+
parameters: [
|
42
|
+
Parameter.new("&block", type: "T.proc.params(e: #{self.model_class_name}).void")
|
43
|
+
],
|
44
|
+
return_type: "T::Array[#{self.model_class_name}]",
|
45
|
+
implementation: true,
|
46
|
+
)
|
47
|
+
class_rbi.create_method(
|
48
|
+
"flatten",
|
49
|
+
parameters: [ Parameter.new("level", type: "T.nilable(Integer)") ],
|
50
|
+
return_type: "T::Array[#{self.model_class_name}]",
|
51
|
+
)
|
52
|
+
# this is an escape hatch when there are conflicts in signatures of Enumerable & ActiveRecord
|
53
|
+
class_rbi.create_method(
|
54
|
+
"to_a",
|
55
|
+
return_type: "T::Array[#{self.model_class_name}]",
|
56
|
+
)
|
57
|
+
# TODO use type_parameters(:U) when parlour supports it
|
58
|
+
class_rbi.create_arbitrary(
|
59
|
+
code: <<~RUBY
|
60
|
+
sig do
|
61
|
+
type_parameters(:U).params(
|
62
|
+
blk: T.proc.params(arg0: Elem).returns(T.type_parameter(:U)),
|
63
|
+
)
|
64
|
+
.returns(T::Array[T.type_parameter(:U)])
|
65
|
+
end
|
66
|
+
def map(&blk); end
|
67
|
+
RUBY
|
68
|
+
)
|
69
|
+
end
|
64
70
|
end
|
65
71
|
end
|
@@ -78,6 +78,12 @@ module SorbetRails::ModelPlugins
|
|
78
78
|
when :elastic_search
|
79
79
|
require('sorbet-rails/gem_plugins/elastic_search_plugin')
|
80
80
|
ElasticSearchPlugin
|
81
|
+
when :active_flag
|
82
|
+
require('sorbet-rails/gem_plugins/active_flag_plugin')
|
83
|
+
ActiveFlagPlugin
|
84
|
+
when :paperclip
|
85
|
+
require('sorbet-rails/gem_plugins/paperclip_plugin')
|
86
|
+
PaperclipPlugin
|
81
87
|
else
|
82
88
|
raise UnrecognizedPluginName.new(
|
83
89
|
"Unrecognized plugin with name: #{plugin_name}. Please check available plugins in the
|
@@ -31,6 +31,10 @@ class SorbetRails::RoutesRbiFormatter
|
|
31
31
|
@parlour.root.create_module('ActionView::Helpers') do |mod|
|
32
32
|
mod.create_include('GeneratedUrlHelpers')
|
33
33
|
end
|
34
|
+
|
35
|
+
@parlour.root.create_class('ActionMailer::Base') do |mod|
|
36
|
+
mod.create_include('GeneratedUrlHelpers')
|
37
|
+
end
|
34
38
|
end
|
35
39
|
|
36
40
|
sig { params(routes: T.untyped, filter: T.untyped).void }
|
@@ -37,6 +37,7 @@ namespace :rails_rbi do
|
|
37
37
|
copy_bundled_rbi('type_assert.rbi')
|
38
38
|
copy_bundled_rbi('parameters.rbi')
|
39
39
|
copy_bundled_rbi('pluck_to_tstruct.rbi')
|
40
|
+
copy_bundled_rbi('active_record_relation.rbi')
|
40
41
|
end
|
41
42
|
|
42
43
|
desc "Generate rbis for rails models. Pass models name to regenerate rbi for only the given models."
|
@@ -113,7 +114,7 @@ namespace :rails_rbi do
|
|
113
114
|
begin
|
114
115
|
formatter = SorbetRails::ModelRbiFormatter.new(model_class, available_class_names)
|
115
116
|
[model_class.name, formatter.generate_rbi]
|
116
|
-
rescue StandardError => ex
|
117
|
+
rescue StandardError, NotImplementedError => ex
|
117
118
|
puts "---"
|
118
119
|
puts "Error when handling model #{model_class.name}: #{ex}"
|
119
120
|
nil
|
data/sorbet-rails.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = %q{sorbet-rails}
|
3
|
-
s.version = "0.5.
|
3
|
+
s.version = "0.5.9"
|
4
4
|
s.date = %q{2019-04-18}
|
5
5
|
s.summary = %q{Set of tools to make Sorbet work with Rails seamlessly.}
|
6
6
|
s.authors = ["Chan Zuckerberg Initiative"]
|
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
`git ls-files -z`.split("\x0").select { |f| f.match(%r{^(test|spec|features)/}) }
|
16
16
|
end
|
17
17
|
|
18
|
-
s.add_dependency 'parlour', '~>
|
18
|
+
s.add_dependency 'parlour', '~> 1.0'
|
19
19
|
|
20
20
|
# Development
|
21
21
|
s.add_development_dependency 'rspec', '~> 3.8', '>= 3.8'
|