sorbet-rails 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +18 -17
- data/.travis.yml +19 -0
- data/Gemfile +15 -0
- data/README.md +36 -20
- data/lib/sorbet-rails/model_rbi_formatter.rb +74 -85
- data/lib/sorbet-rails/routes_rbi_formatter.rb +9 -8
- data/lib/sorbet-rails/tasks/rails_rbi.rake +1 -0
- data/{rbis → rbi}/activerecord.rbi +17 -0
- data/sorbet-rails.gemspec +6 -7
- data/spec/bin/reset_test_data.sh +5 -0
- data/spec/bin/run_all_specs.sh +9 -0
- data/spec/model_rbi_formatter_spec.rb +14 -16
- data/spec/rails_helper.rb +25 -1
- data/spec/routes_rbi_formatter_spec.rb +12 -6
- data/spec/spec_helper.rb +5 -0
- data/spec/support/5.1.7/Gemfile +41 -0
- data/spec/support/{rails_app → 5.1.7}/README.md +0 -0
- data/spec/support/{rails_app → 5.1.7}/Rakefile +0 -0
- data/spec/support/{rails_app → 5.1.7}/app/channels/application_cable/channel.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/app/channels/application_cable/connection.rb +0 -0
- data/spec/support/5.1.7/app/controllers +1 -0
- data/spec/support/{rails_app → 5.1.7}/app/jobs/application_job.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/app/mailers/application_mailer.rb +0 -0
- data/spec/support/5.1.7/app/models +1 -0
- data/spec/support/{rails_app → 5.1.7}/app/views/layouts/mailer.html.erb +0 -0
- data/spec/support/{rails_app → 5.1.7}/app/views/layouts/mailer.text.erb +0 -0
- data/spec/support/5.1.7/bin/bundle +3 -0
- data/spec/support/{rails_app → 5.1.7}/bin/rails +0 -0
- data/spec/support/{rails_app → 5.1.7}/bin/rake +0 -0
- data/spec/support/5.1.7/bin/setup +35 -0
- data/spec/support/{rails_app → 5.1.7}/bin/spring +0 -0
- data/spec/support/5.1.7/bin/update +29 -0
- data/spec/support/{rails_app → 5.1.7}/config.ru +0 -0
- data/spec/support/5.1.7/config/application.rb +33 -0
- data/spec/support/{rails_app → 5.1.7}/config/boot.rb +0 -0
- data/spec/support/5.1.7/config/cable.yml +10 -0
- data/spec/support/{rails_app → 5.1.7}/config/database.yml +0 -0
- data/spec/support/{rails_app → 5.1.7}/config/environment.rb +0 -0
- data/spec/support/5.1.7/config/environments/development.rb +47 -0
- data/spec/support/5.1.7/config/environments/production.rb +83 -0
- data/spec/support/5.1.7/config/environments/test.rb +42 -0
- data/spec/support/{rails_app → 5.1.7}/config/initializers/application_controller_renderer.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/config/initializers/backtrace_silencers.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/config/initializers/cors.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/config/initializers/filter_parameter_logging.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/config/initializers/inflections.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/config/initializers/mime_types.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/config/initializers/wrap_parameters.rb +0 -0
- data/spec/support/{rails_app → 5.1.7}/config/locales/en.yml +0 -0
- data/spec/support/5.1.7/config/puma.rb +56 -0
- data/spec/support/5.1.7/config/routes.rb +1 -0
- data/spec/support/5.1.7/config/secrets.yml +32 -0
- data/spec/support/5.1.7/config/spring.rb +6 -0
- data/spec/support/5.1.7/db/migrate +1 -0
- data/spec/support/{rails_app → 5.1.7}/db/schema.rb +7 -1
- data/spec/support/{rails_app → 5.1.7}/db/seeds.rb +0 -0
- data/spec/support/{rails_app/app/controllers/concerns → 5.1.7/lib/tasks}/.keep +0 -0
- data/spec/support/{rails_app/app/models/concerns → 5.1.7/log}/.keep +0 -0
- data/spec/support/{rails_app → 5.1.7}/public/robots.txt +0 -0
- data/spec/support/{rails_app/lib/tasks → 5.1.7/test/controllers}/.keep +0 -0
- data/spec/support/{rails_app/log → 5.1.7/test/fixtures}/.keep +0 -0
- data/spec/support/{rails_app/storage → 5.1.7/test/fixtures/files}/.keep +0 -0
- data/spec/support/{rails_app/test/controllers → 5.1.7/test/integration}/.keep +0 -0
- data/spec/support/{rails_app/test/fixtures → 5.1.7/test/mailers}/.keep +0 -0
- data/spec/support/{rails_app/test/fixtures/files → 5.1.7/test/models}/.keep +0 -0
- data/spec/support/5.1.7/test/test_helper.rb +10 -0
- data/spec/support/{rails_app/test/integration → 5.1.7/tmp}/.keep +0 -0
- data/spec/support/{rails_app/test/mailers → 5.1.7/vendor}/.keep +0 -0
- data/spec/support/{rails_app → 5.2.3}/Gemfile +13 -2
- data/spec/support/5.2.3/README.md +24 -0
- data/spec/support/5.2.3/Rakefile +6 -0
- data/spec/support/5.2.3/app/channels/application_cable/channel.rb +4 -0
- data/spec/support/5.2.3/app/channels/application_cable/connection.rb +4 -0
- data/spec/support/5.2.3/app/controllers +1 -0
- data/spec/support/5.2.3/app/jobs/application_job.rb +2 -0
- data/spec/support/5.2.3/app/mailers/application_mailer.rb +4 -0
- data/spec/support/5.2.3/app/models +1 -0
- data/spec/support/5.2.3/app/views/layouts/mailer.html.erb +13 -0
- data/spec/support/5.2.3/app/views/layouts/mailer.text.erb +1 -0
- data/spec/support/{rails_app → 5.2.3}/bin/bundle +0 -0
- data/spec/support/5.2.3/bin/rails +9 -0
- data/spec/support/5.2.3/bin/rake +9 -0
- data/spec/support/{rails_app → 5.2.3}/bin/setup +0 -0
- data/spec/support/5.2.3/bin/spring +17 -0
- data/spec/support/{rails_app → 5.2.3}/bin/update +0 -0
- data/spec/support/5.2.3/config.ru +5 -0
- data/spec/support/{rails_app → 5.2.3}/config/application.rb +0 -0
- data/spec/support/5.2.3/config/boot.rb +3 -0
- data/spec/support/{rails_app → 5.2.3}/config/cable.yml +0 -0
- data/spec/support/{rails_app → 5.2.3}/config/credentials.yml.enc +0 -0
- data/spec/support/5.2.3/config/database.yml +25 -0
- data/spec/support/5.2.3/config/environment.rb +5 -0
- data/spec/support/{rails_app → 5.2.3}/config/environments/development.rb +0 -0
- data/spec/support/{rails_app → 5.2.3}/config/environments/production.rb +0 -0
- data/spec/support/{rails_app → 5.2.3}/config/environments/test.rb +0 -0
- data/spec/support/5.2.3/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/support/5.2.3/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/support/5.2.3/config/initializers/cors.rb +16 -0
- data/spec/support/5.2.3/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/support/5.2.3/config/initializers/inflections.rb +16 -0
- data/spec/support/5.2.3/config/initializers/mime_types.rb +4 -0
- data/spec/support/5.2.3/config/initializers/wrap_parameters.rb +14 -0
- data/spec/support/5.2.3/config/locales/en.yml +33 -0
- data/spec/support/{rails_app → 5.2.3}/config/puma.rb +0 -0
- data/spec/support/5.2.3/config/routes.rb +1 -0
- data/spec/support/{rails_app → 5.2.3}/config/spring.rb +0 -0
- data/spec/support/{rails_app → 5.2.3}/config/storage.yml +0 -0
- data/spec/support/5.2.3/db/migrate +1 -0
- data/spec/support/5.2.3/db/schema.rb +47 -0
- data/spec/support/5.2.3/db/seeds.rb +7 -0
- data/spec/support/{rails_app/test/models → 5.2.3/lib/tasks}/.keep +0 -0
- data/spec/support/{rails_app/tmp → 5.2.3/log}/.keep +0 -0
- data/spec/support/5.2.3/public/robots.txt +1 -0
- data/spec/support/{rails_app/vendor → 5.2.3/storage}/.keep +0 -0
- data/spec/{test_data/expected_wizard_no_book.rbi → support/5.2.3/test/controllers/.keep} +0 -0
- data/spec/support/5.2.3/test/fixtures/.keep +0 -0
- data/spec/support/5.2.3/test/fixtures/files/.keep +0 -0
- data/spec/support/5.2.3/test/integration/.keep +0 -0
- data/spec/support/5.2.3/test/mailers/.keep +0 -0
- data/spec/support/5.2.3/test/models/.keep +0 -0
- data/spec/support/{rails_app → 5.2.3}/test/test_helper.rb +0 -0
- data/spec/support/5.2.3/tmp/.keep +0 -0
- data/spec/support/5.2.3/vendor/.keep +0 -0
- data/spec/support/{rails_app → rails_shared}/app/controllers/application_controller.rb +0 -0
- data/spec/support/rails_shared/app/controllers/concerns/.keep +0 -0
- data/spec/support/{rails_app → rails_shared}/app/models/application_record.rb +2 -0
- data/spec/support/rails_shared/app/models/potion.rb +5 -0
- data/spec/support/{rails_app → rails_shared}/app/models/spell_book.rb +0 -0
- data/spec/support/{rails_app → rails_shared}/app/models/wand.rb +0 -0
- data/spec/support/{rails_app → rails_shared}/app/models/wizard.rb +0 -0
- data/spec/support/{rails_app → rails_shared}/config/routes.rb +0 -0
- data/spec/support/{rails_app → rails_shared}/db/migrate/20190620001234_create_wizards.rb +1 -1
- data/spec/support/{rails_app → rails_shared}/db/migrate/20190620003037_create_wands.rb +1 -1
- data/spec/support/{rails_app → rails_shared}/db/migrate/20190620003739_create_spell_books.rb +1 -1
- data/spec/support/rails_shared/db/migrate/20190622000000_add_more_column_types_to_wands.rb +15 -0
- data/spec/test_data/5.1.7/expected_no_routes.rbi +11 -0
- data/spec/test_data/5.1.7/expected_potion.rbi +126 -0
- data/spec/test_data/5.1.7/expected_routes.rbi +13 -0
- data/spec/test_data/{models → 5.1.7}/expected_wand.rbi +66 -6
- data/spec/test_data/{models → 5.1.7}/expected_wizard.rbi +26 -8
- data/spec/test_data/5.1.7/expected_wizard_wo_spellbook.rbi +207 -0
- data/spec/test_data/5.2.3/expected_no_routes.rbi +11 -0
- data/spec/test_data/5.2.3/expected_potion.rbi +126 -0
- data/spec/test_data/{expected_routes.rbi → 5.2.3/expected_routes.rbi} +0 -0
- data/spec/test_data/5.2.3/expected_wand.rbi +246 -0
- data/spec/test_data/{models/expected_wizard_wo_spellbook.rbi → 5.2.3/expected_wizard.rbi} +26 -8
- data/spec/test_data/5.2.3/expected_wizard_wo_spellbook.rbi +207 -0
- metadata +277 -193
- data/Gemfile.lock +0 -176
- data/spec/support/rails_app/.ruby-version +0 -1
- data/spec/support/rails_app/Gemfile.lock +0 -139
- data/spec/support/rails_app/app/controllers/test_controller.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f46a2ecda33e62dbb1410943780827a098f5019d
|
4
|
+
data.tar.gz: fe0478758bf1882d6ef2749d9b1cdf56a96afebd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a194e4ea66176b304fae0b7bffa5ab1b301ba02d1cc2c9d6697538e45082d1ff0a52a57cc2e9f666764bcbf30a0e62c8498ec4b7c5a6952fb91d7cab506ea09
|
7
|
+
data.tar.gz: 3a2727165b5963f0b9ea9f067053330e00fe829ab5ab3f9ac098319c74e149565d8cf244f297d3aab1706f5a2fc5882a8a1978d88d6b01a7dd1b92ffe7166031
|
data/.gitignore
CHANGED
@@ -42,36 +42,37 @@ build-iPhoneSimulator/
|
|
42
42
|
|
43
43
|
# for a library or gem, you might want to ignore these files since the code is
|
44
44
|
# intended to run in multiple environments; otherwise, check them in:
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
Gemfile.lock
|
46
|
+
.ruby-version
|
47
|
+
.ruby-gemset
|
48
48
|
|
49
49
|
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
50
|
.rvmrc
|
51
51
|
|
52
52
|
# Rails app
|
53
|
-
/spec/support
|
54
|
-
/spec/support
|
53
|
+
/spec/support/**/tmp/*
|
54
|
+
/spec/support/**/.bundle
|
55
55
|
|
56
56
|
# Ignore the default SQLite database.
|
57
|
-
/spec/support
|
58
|
-
/spec/support
|
57
|
+
/spec/support/**/db/*.sqlite3
|
58
|
+
/spec/support/**/db/*.sqlite3-journal
|
59
59
|
|
60
60
|
# Ignore all logfiles and tempfiles.
|
61
|
-
/spec/support
|
62
|
-
/spec/support
|
63
|
-
!/spec/support
|
64
|
-
!/spec/support
|
61
|
+
/spec/support/**/log/*
|
62
|
+
/spec/support/**/tmp/*
|
63
|
+
!/spec/support/**/log/.keep
|
64
|
+
!/spec/support/**/tmp/.keep
|
65
65
|
|
66
66
|
# Ignore uploaded files in development
|
67
|
-
/spec/support
|
68
|
-
!/spec/support
|
67
|
+
/spec/support/**/storage/*
|
68
|
+
!/spec/support/**/storage/.keep
|
69
69
|
|
70
|
-
/spec/support
|
71
|
-
/spec/support
|
70
|
+
/spec/support/**/node_modules
|
71
|
+
/spec/support/**/yarn-error.log
|
72
72
|
|
73
|
-
/spec/support
|
73
|
+
/spec/support/**/public/assets
|
74
74
|
.byebug_history
|
75
75
|
|
76
76
|
# Ignore master key for decrypting credentials and more.
|
77
|
-
/spec/support
|
77
|
+
/spec/support/**/config/master.key
|
78
|
+
|
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
language: ruby
|
2
|
+
env:
|
3
|
+
- "RAILS_VERSION=5.1.7"
|
4
|
+
- "RAILS_VERSION=5.2.3"
|
5
|
+
rvm:
|
6
|
+
- 2.4.3
|
7
|
+
- 2.5.0
|
8
|
+
before_install:
|
9
|
+
- gem install bundler -v 2.0.1 --no-doc
|
10
|
+
script:
|
11
|
+
- bundle exec rake spec
|
12
|
+
deploy:
|
13
|
+
provider: rubygems
|
14
|
+
api_key:
|
15
|
+
secure: XWpjZ+CzAqa9I/S9xJCFoCYJQ3cVp2Dz3DIWNjfC4H4lhxZokGA0FW/dKS2GFj/hadvw9gN5YYshyJegzm5I024QFexRxdBrmlVYCic/gqRIuK7hNp1IFeQJYK4j2s6HFcYnqn1sRT2EJQjdQVG/2+Dmv46xlpEPZH5yVnmQlIln28mYvXjPAjHgOLewgj7QEvt72jASd93X+YT30wGugVK0F+lWG75VFWBECe4kYebyNJZ+REX4aKnejtQwIhERD6vRHge3XZ2HQ0o0AZpqL8xK3ghjSuV3XOes01zvpg0qnsf4YA+ZaIRDmYPpCoWBoVFw2M4AyUUIHH0olojt0kDZvhhanDOk3pAxHaEN1bp0rs8oH5olXFnAXOm+4tls/ZyUPKCX/6UgibMVn99LOIxESx0fCf6N81rlHbD8lczLYDOkatozqoDYykt2I8mErYJjZSntwzRwRATkbWxb8Kgx60arUdWwzWXyNdN2Cz4Y0wnuY59X80/d7IddJoWClk2YSkyJqeASQytR2WfjRUkuCZnxOWjccajwRl5fOkA2md2Ttq92OZktqYaAz0l2FcVrxcPiGi5aQk+t8ygBtJx9ft2DQvyZQ1W8Sxdox06ql/WXDhq5i/gjq/jhN7Uu7Xs3G5UvxL+OO/2YQqy8rqb4IKuYAOLoNFmFDEQoN2Q=
|
16
|
+
gem: sorbet-rails
|
17
|
+
on:
|
18
|
+
tags: true
|
19
|
+
repo: chanzuckerberg/sorbet-rails
|
data/Gemfile
CHANGED
@@ -4,4 +4,19 @@ gemspec
|
|
4
4
|
|
5
5
|
group :test do
|
6
6
|
gem 'simplecov', require: false
|
7
|
+
gem 'codecov', require: false
|
7
8
|
end
|
9
|
+
|
10
|
+
rails_version = ENV["RAILS_VERSION"] || "default"
|
11
|
+
|
12
|
+
rails =
|
13
|
+
case rails_version
|
14
|
+
when "master"
|
15
|
+
{github: "rails/rails"}
|
16
|
+
when "default"
|
17
|
+
">= 5.2.3"
|
18
|
+
else
|
19
|
+
"~> #{rails_version}"
|
20
|
+
end
|
21
|
+
|
22
|
+
gem "rails", rails
|
data/README.md
CHANGED
@@ -1,48 +1,61 @@
|
|
1
1
|
# sorbet-rails
|
2
|
-
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/sorbet-rails.svg)](https://badge.fury.io/rb/sorbet-rails)
|
3
|
+
[![Build Status](https://travis-ci.org/chanzuckerberg/sorbet-rails.svg?branch=master)](https://travis-ci.org/chanzuckerberg/sorbet-rails)
|
4
|
+
[![codecov](https://codecov.io/gh/chanzuckerberg/sorbet-rails/branch/master/graph/badge.svg)](https://codecov.io/gh/chanzuckerberg/sorbet-rails)
|
5
|
+
|
6
|
+
A set of tools to make the [Sorbet](https://sorbet.org) typechecker work with Ruby on Rails seamlessly.
|
3
7
|
|
4
8
|
This gem adds a few Rake tasks to generate Ruby Interface (RBI) files for dynamic methods generated by Rails. It also includes signatures for related Rails classes. The RBI files are added to a `sorbet/rails-rbi/` folder.
|
5
9
|
|
6
10
|
## Initial Setup
|
7
11
|
|
8
|
-
1.
|
12
|
+
1. Follow the steps [here](https://sorbet.org/docs/adopting) to set up the latest version of Sorbet, up to being able to run `srb tc`.
|
13
|
+
|
14
|
+
2. Add `sorbet-rails` to your Gemfile and install them with Bundler.
|
15
|
+
|
16
|
+
```
|
17
|
+
# -- Gemfile --
|
18
|
+
|
19
|
+
gem 'sorbet-rails'
|
20
|
+
```
|
9
21
|
|
10
|
-
2. Include the ActiveRecord RBI file:
|
11
22
|
```sh
|
12
|
-
|
23
|
+
❯ bundle install
|
13
24
|
```
|
14
|
-
|
25
|
+
|
26
|
+
3. Generate RBI files for your routes and models:
|
15
27
|
```sh
|
16
|
-
|
17
|
-
|
28
|
+
❯ rake rails_rbi:routes
|
29
|
+
❯ rake rails_rbi:models
|
18
30
|
```
|
31
|
+
|
19
32
|
4. Auto-upgrade the typecheck level of files:
|
20
33
|
```sh
|
21
|
-
|
34
|
+
❯ srb tc --suggest-typed --typed=strict --error-white-list=7022 --autocorrect
|
22
35
|
```
|
23
|
-
Because we've generated RBI files for routes and models, a lot more files should be
|
36
|
+
Because we've generated RBI files for routes and models, a lot more files should be typecheckable now.
|
24
37
|
|
25
38
|
## RBI Files
|
26
39
|
|
27
|
-
### ActiveRecord
|
40
|
+
### ActiveRecord
|
28
41
|
|
29
|
-
There is an ActiveRecord RBI file that we vendor with this gem.
|
42
|
+
There is an ActiveRecord RBI file that we vendor with this gem. Sorbet picks up these vendored RBI files automatically. (Please make sure you are running the latest version.)
|
30
43
|
|
31
|
-
### Routes
|
44
|
+
### Routes
|
32
45
|
|
33
46
|
This Rake task generates an RBI file defining `_path` and `_url` methods for all named routes in `routes.rb`:
|
34
47
|
```sh
|
35
|
-
|
48
|
+
❯ rake rails_rbi:routes
|
36
49
|
```
|
37
|
-
### Models
|
50
|
+
### Models
|
38
51
|
|
39
52
|
This Rake task generates RBI files for all models in the Rails application (all descendants of `ActiveRecord::Base`):
|
40
53
|
```sh
|
41
|
-
|
54
|
+
❯ rake rails_rbi:models
|
42
55
|
```
|
43
56
|
You can also regenerate RBI files for specific models:
|
44
57
|
```sh
|
45
|
-
|
58
|
+
❯ rake rails_rbi:models[ModelName,AnotherOne,...]
|
46
59
|
```
|
47
60
|
The generation task currently creates the following signatures:
|
48
61
|
- Column getters & setters
|
@@ -51,11 +64,14 @@ The generation task currently creates the following signatures:
|
|
51
64
|
- Named scopes
|
52
65
|
- Model relation class
|
53
66
|
|
67
|
+
```
|
68
|
+
Skip method ... because it is not autogenerated by Rails.
|
69
|
+
```
|
54
70
|
## Tips & Tricks
|
55
71
|
|
56
|
-
### `find`, `first` and `last`
|
72
|
+
### `find`, `first` and `last`
|
57
73
|
|
58
|
-
These 3 methods can either return a single nilable record or an array of records. Sorbet does not allow us to define multiple signatures for a function. It doesn't support defining one function signature that has varying returning value depending on the input parameter type. We opt to define the most commonly used signature for these methods, and monkey-patch new functions for the secondary use case.
|
74
|
+
These 3 methods can either return a single nilable record or an array of records. Sorbet does not allow us to define multiple signatures for a function ([except stdlib](https://github.com/chanzuckerberg/sorbet-rails/issues/18)). It doesn't support defining one function signature that has varying returning value depending on the input parameter type. We opt to define the most commonly used signature for these methods, and monkey-patch new functions for the secondary use case.
|
59
75
|
|
60
76
|
In short:
|
61
77
|
- Use `find`, `first` and `last` to fetch a single record.
|
@@ -90,12 +106,12 @@ end
|
|
90
106
|
If you wanted to make these changes using [Codemod](https://github.com/facebook/codemod), try these commands:
|
91
107
|
```shell
|
92
108
|
# from methods like after_commit do <...> end
|
93
|
-
codemod -d app/models/ --extensions rb \
|
109
|
+
❯ codemod -d app/models/ --extensions rb \
|
94
110
|
'(\s*)(before|after)_(validation|save|create|commit|find|initialize|destroy) do' \
|
95
111
|
'\1\2_\3 :\2_\3\n\1def \2_\3'
|
96
112
|
|
97
113
|
# from methods like after_commit { <...> }
|
98
|
-
codemod -d app/models/ --extensions rb \
|
114
|
+
❯ codemod -d app/models/ --extensions rb \
|
99
115
|
'(\s*)(before|after)_(validation|save|create|commit|find|initialize|destroy) \{ (.*) \}' \
|
100
116
|
'\1\2_\3 :\2_\3\n\1def \2_\3\n\1\1\4\n\1end'
|
101
117
|
```
|
@@ -1,29 +1,20 @@
|
|
1
1
|
# typed: true
|
2
2
|
class ModelRbiFormatter
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
date: 'Date',
|
7
|
-
datetime: 'DateTime',
|
8
|
-
decimal: 'Integer',
|
9
|
-
integer: 'Integer',
|
10
|
-
string: 'String',
|
11
|
-
text: 'String',
|
12
|
-
json: 'Hash',
|
13
|
-
jsonb: 'Hash',
|
14
|
-
}
|
3
|
+
MODEL_RELATION_SHARED_MODULE_SUFFIX = "ModelRelationShared"
|
4
|
+
MODEL_CLASS_MODULE_SUFFIX = "ClassMethods"
|
5
|
+
MODEL_INSTANCE_MODULE_SUFFIX = "InstanceMethods"
|
15
6
|
|
16
7
|
def initialize(model_class, available_classes)
|
17
8
|
@model_class = model_class
|
18
9
|
@available_classes = available_classes
|
19
|
-
@columns_hash = model_class.columns_hash
|
10
|
+
@columns_hash = model_class.table_exists? ? model_class.columns_hash : {}
|
20
11
|
@generated_sigs = ActiveSupport::HashWithIndifferentAccess.new
|
21
12
|
@generated_class_sigs = ActiveSupport::HashWithIndifferentAccess.new
|
22
13
|
@generated_scope_sigs = ActiveSupport::HashWithIndifferentAccess.new
|
23
14
|
@generated_querying_sigs = ActiveSupport::HashWithIndifferentAccess.new
|
24
15
|
begin
|
25
16
|
# Load all dynamic instance methods of this model by instantiating a fake model
|
26
|
-
@model_class.new
|
17
|
+
@model_class.new unless @model_class.abstract_class?
|
27
18
|
rescue StandardError
|
28
19
|
puts "Note: Unable to create new instance of #{model_class.name}"
|
29
20
|
end
|
@@ -38,33 +29,34 @@ class ModelRbiFormatter
|
|
38
29
|
populate_generated_enum_methods
|
39
30
|
|
40
31
|
@buffer = []
|
41
|
-
@buffer <<
|
32
|
+
@buffer << draw_file_header_and_base_classes
|
42
33
|
|
34
|
+
@buffer << draw_module_header("#{@model_class.name}::#{MODEL_INSTANCE_MODULE_SUFFIX}")
|
43
35
|
@model_class.instance_methods.sort.each do |method_name|
|
44
36
|
expected_sig = @generated_sigs[method_name]
|
45
37
|
next unless expected_sig.present?
|
46
38
|
method_obj = @model_class.instance_method(method_name)
|
47
|
-
draw_method(method_name, method_obj, expected_sig
|
39
|
+
draw_method(method_name, method_obj, expected_sig)
|
48
40
|
end
|
41
|
+
@buffer << draw_module_or_class_footer
|
49
42
|
|
43
|
+
@buffer << draw_module_header("#{@model_class.name}::#{MODEL_CLASS_MODULE_SUFFIX}")
|
50
44
|
@model_class.methods.sort.each do |method_name|
|
51
45
|
expected_sig = @generated_class_sigs[method_name]
|
52
46
|
next unless expected_sig.present?
|
53
47
|
method_obj = @model_class.method(method_name)
|
54
|
-
draw_method(method_name, method_obj, expected_sig
|
48
|
+
draw_method(method_name, method_obj, expected_sig)
|
55
49
|
end
|
50
|
+
@buffer << draw_module_or_class_footer
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
# in this module, it'll be added to both the Model class as a class method
|
61
|
-
# and to its relation as an instance method.
|
52
|
+
# <Model>::MODEL_RELATION_SHARED_MODULE_SUFFIX is a fake module added so that
|
53
|
+
# when a method is defined in this module, it'll be added to both the Model class
|
54
|
+
# as a class method and to its relation as an instance method.
|
62
55
|
#
|
63
|
-
# We need to define the
|
56
|
+
# We need to define the module after the other classes
|
64
57
|
# to work around Sorbet loading order bug
|
65
58
|
# https://sorbet-ruby.slack.com/archives/CHN2L03NH/p1556065791047300
|
66
|
-
@buffer << "
|
67
|
-
@buffer << draw_named_scope_header
|
59
|
+
@buffer << draw_module_header("#{@model_class.name}::#{MODEL_RELATION_SHARED_MODULE_SUFFIX}")
|
68
60
|
# For simplicity, generate both in the same module for now.
|
69
61
|
# We don't need to define two fake modules to share methods between <Model> and <Relation>
|
70
62
|
({}.
|
@@ -72,29 +64,20 @@ class ModelRbiFormatter
|
|
72
64
|
merge(@generated_querying_sigs)
|
73
65
|
).each do |method_name, expected_sig|
|
74
66
|
method_obj = @model_class.method(method_name) if @model_class.methods.include?(method_name.to_sym)
|
75
|
-
# this is not a class method because it is added to
|
76
|
-
draw_method(method_name, method_obj, expected_sig
|
67
|
+
# this is not a class method because it is added to a module
|
68
|
+
draw_method(method_name, method_obj, expected_sig)
|
77
69
|
end
|
78
|
-
@buffer <<
|
79
|
-
@buffer << "\n"
|
70
|
+
@buffer << draw_module_or_class_footer
|
80
71
|
@buffer.join("\n")
|
81
72
|
end
|
82
73
|
|
83
|
-
def draw_method(method_name, method_obj, expected_sig
|
74
|
+
def draw_method(method_name, method_obj, expected_sig)
|
84
75
|
if !method_obj.present?
|
85
76
|
# not very actionable because this could be a method in a newer version of Rails
|
86
77
|
# puts "Skip method '#{method_name}' because there is no matching method object."
|
87
78
|
return
|
88
79
|
end
|
89
|
-
|
90
|
-
puts "Skip method '#{method_name}' because it is not autogenerated by Rails."
|
91
|
-
return
|
92
|
-
end
|
93
|
-
if !matched_signature?(method_obj, expected_sig)
|
94
|
-
puts "Skip method '#{method_name}' because it has different signature from expected."
|
95
|
-
return
|
96
|
-
end
|
97
|
-
@buffer << generate_method_sig(method_name, expected_sig, is_class_method).indent(2)
|
80
|
+
@buffer << generate_method_sig(method_name, expected_sig).indent(2)
|
98
81
|
end
|
99
82
|
|
100
83
|
def populate_activerecord_querying_methods
|
@@ -203,8 +186,8 @@ class ModelRbiFormatter
|
|
203
186
|
# TODO allow people to specify the possible values of polymorphic associations
|
204
187
|
assoc_class = assoc_should_be_untyped?(reflection) ? "T.untyped" : reflection.class_name
|
205
188
|
relation_class = relation_should_be_untyped?(reflection) ?
|
206
|
-
"ActiveRecord::
|
207
|
-
"#{assoc_class}::
|
189
|
+
"ActiveRecord::Associations::CollectionProxy[T.untyped]" :
|
190
|
+
"#{assoc_class}::CollectionProxy"
|
208
191
|
@generated_sigs.merge!({
|
209
192
|
"#{assoc_name}" => { ret: relation_class },
|
210
193
|
"#{assoc_name}=" => {
|
@@ -241,7 +224,7 @@ class ModelRbiFormatter
|
|
241
224
|
reflection.polymorphic?
|
242
225
|
end
|
243
226
|
|
244
|
-
def
|
227
|
+
def draw_file_header_and_base_classes
|
245
228
|
# We define a custom <ModelName>::Relation class so that it can be extended
|
246
229
|
# to contain custom scopes for each models
|
247
230
|
<<~MESSAGE
|
@@ -250,7 +233,13 @@ class ModelRbiFormatter
|
|
250
233
|
# typed: strong
|
251
234
|
|
252
235
|
class #{@model_class.name}::Relation < ActiveRecord::Relation
|
253
|
-
include #{@model_class.name}
|
236
|
+
include #{@model_class.name}::#{MODEL_RELATION_SHARED_MODULE_SUFFIX}
|
237
|
+
extend T::Generic
|
238
|
+
Elem = type_member(fixed: #{@model_class.name})
|
239
|
+
end
|
240
|
+
|
241
|
+
class #{@model_class.name}::CollectionProxy < ActiveRecord::Associations::CollectionProxy
|
242
|
+
include #{@model_class.name}::#{MODEL_RELATION_SHARED_MODULE_SUFFIX}
|
254
243
|
extend T::Generic
|
255
244
|
Elem = type_member(fixed: #{@model_class.name})
|
256
245
|
end
|
@@ -258,26 +247,27 @@ class ModelRbiFormatter
|
|
258
247
|
class #{@model_class.name} < #{@model_class.superclass}
|
259
248
|
extend T::Sig
|
260
249
|
extend T::Generic
|
261
|
-
extend #{@model_class.name}
|
250
|
+
extend #{@model_class.name}::#{MODEL_RELATION_SHARED_MODULE_SUFFIX}
|
251
|
+
extend #{@model_class.name}::ClassMethods
|
252
|
+
include #{@model_class.name}::InstanceMethods
|
262
253
|
Elem = type_template(fixed: #{@model_class.name})
|
254
|
+
end
|
263
255
|
MESSAGE
|
264
256
|
end
|
265
257
|
|
266
|
-
def
|
267
|
-
|
258
|
+
def draw_module_or_class_footer
|
259
|
+
<<~MESSAGE
|
260
|
+
end
|
261
|
+
MESSAGE
|
268
262
|
end
|
269
263
|
|
270
|
-
def
|
264
|
+
def draw_module_header(name)
|
271
265
|
<<~MESSAGE
|
272
|
-
module #{
|
266
|
+
module #{name}
|
273
267
|
extend T::Sig
|
274
268
|
MESSAGE
|
275
269
|
end
|
276
270
|
|
277
|
-
def draw_named_scope_footer
|
278
|
-
"end"
|
279
|
-
end
|
280
|
-
|
281
271
|
def generate_column_methods(buffer)
|
282
272
|
@columns_hash.each do |column_name, column_def|
|
283
273
|
buffer << draw_column_methods(column_name, column_def)
|
@@ -285,44 +275,45 @@ class ModelRbiFormatter
|
|
285
275
|
end
|
286
276
|
|
287
277
|
def type_for_column_def(column_def)
|
288
|
-
|
278
|
+
cast_type = ActiveRecord::Base.connection.lookup_cast_type_from_column(column_def)
|
279
|
+
strict_type = active_record_type_to_sorbet_type(cast_type)
|
280
|
+
|
289
281
|
if column_def.respond_to?(:array?) && column_def.array?
|
290
282
|
strict_type = "T::Array[#{strict_type}]"
|
291
283
|
end
|
292
284
|
column_def.null ? "T.nilable(#{strict_type})" : strict_type
|
293
285
|
end
|
294
286
|
|
295
|
-
def
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
actual_params == expected_params
|
287
|
+
def active_record_type_to_sorbet_type(klass)
|
288
|
+
case klass
|
289
|
+
when ActiveRecord::Type::Boolean
|
290
|
+
"T::Boolean"
|
291
|
+
when ActiveRecord::Type::DateTime
|
292
|
+
DateTime
|
293
|
+
when ActiveRecord::Type::Date
|
294
|
+
Date
|
295
|
+
when ActiveRecord::Type::Decimal
|
296
|
+
BigDecimal
|
297
|
+
when ActiveRecord::Type::Float
|
298
|
+
Float
|
299
|
+
when ActiveRecord::Type::Time
|
300
|
+
Time
|
301
|
+
when ActiveRecord::Type::BigInteger, ActiveRecord::Type::Integer, ActiveRecord::Type::DecimalWithoutScale, ActiveRecord::Type::UnsignedInteger
|
302
|
+
Integer
|
303
|
+
when ActiveRecord::Enum::EnumType, ActiveRecord::Type::Binary, ActiveRecord::Type::String, ActiveRecord::Type::Text
|
304
|
+
String
|
305
|
+
else
|
306
|
+
# Json type is only supported in Rails 5.2.3 and above
|
307
|
+
case
|
308
|
+
when Object.const_defined?('ActiveRecord::Type::Json') && klass.is_a?(ActiveRecord::Type::Json)
|
309
|
+
"T.any(Array, T::Boolean, Float, Hash, Integer, String)"
|
310
|
+
else
|
311
|
+
"T.untyped"
|
312
|
+
end
|
313
|
+
end
|
323
314
|
end
|
324
315
|
|
325
|
-
def generate_method_sig(method_name, generated_method_def
|
316
|
+
def generate_method_sig(method_name, generated_method_def)
|
326
317
|
# generated_method_def:
|
327
318
|
# {
|
328
319
|
# . ret: <return_type>
|
@@ -357,11 +348,9 @@ class ModelRbiFormatter
|
|
357
348
|
"returns(#{generated_method_def[:ret]})" :
|
358
349
|
"void"
|
359
350
|
|
360
|
-
prefix = is_class_method ? "self." : ""
|
361
|
-
|
362
351
|
<<~MESSAGE
|
363
352
|
sig { #{param_sig}#{return_type} }
|
364
|
-
def #{
|
353
|
+
def #{method_name}(#{param_def}); end
|
365
354
|
MESSAGE
|
366
355
|
end
|
367
356
|
end
|