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.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -17
  3. data/.travis.yml +19 -0
  4. data/Gemfile +15 -0
  5. data/README.md +36 -20
  6. data/lib/sorbet-rails/model_rbi_formatter.rb +74 -85
  7. data/lib/sorbet-rails/routes_rbi_formatter.rb +9 -8
  8. data/lib/sorbet-rails/tasks/rails_rbi.rake +1 -0
  9. data/{rbis → rbi}/activerecord.rbi +17 -0
  10. data/sorbet-rails.gemspec +6 -7
  11. data/spec/bin/reset_test_data.sh +5 -0
  12. data/spec/bin/run_all_specs.sh +9 -0
  13. data/spec/model_rbi_formatter_spec.rb +14 -16
  14. data/spec/rails_helper.rb +25 -1
  15. data/spec/routes_rbi_formatter_spec.rb +12 -6
  16. data/spec/spec_helper.rb +5 -0
  17. data/spec/support/5.1.7/Gemfile +41 -0
  18. data/spec/support/{rails_app → 5.1.7}/README.md +0 -0
  19. data/spec/support/{rails_app → 5.1.7}/Rakefile +0 -0
  20. data/spec/support/{rails_app → 5.1.7}/app/channels/application_cable/channel.rb +0 -0
  21. data/spec/support/{rails_app → 5.1.7}/app/channels/application_cable/connection.rb +0 -0
  22. data/spec/support/5.1.7/app/controllers +1 -0
  23. data/spec/support/{rails_app → 5.1.7}/app/jobs/application_job.rb +0 -0
  24. data/spec/support/{rails_app → 5.1.7}/app/mailers/application_mailer.rb +0 -0
  25. data/spec/support/5.1.7/app/models +1 -0
  26. data/spec/support/{rails_app → 5.1.7}/app/views/layouts/mailer.html.erb +0 -0
  27. data/spec/support/{rails_app → 5.1.7}/app/views/layouts/mailer.text.erb +0 -0
  28. data/spec/support/5.1.7/bin/bundle +3 -0
  29. data/spec/support/{rails_app → 5.1.7}/bin/rails +0 -0
  30. data/spec/support/{rails_app → 5.1.7}/bin/rake +0 -0
  31. data/spec/support/5.1.7/bin/setup +35 -0
  32. data/spec/support/{rails_app → 5.1.7}/bin/spring +0 -0
  33. data/spec/support/5.1.7/bin/update +29 -0
  34. data/spec/support/{rails_app → 5.1.7}/config.ru +0 -0
  35. data/spec/support/5.1.7/config/application.rb +33 -0
  36. data/spec/support/{rails_app → 5.1.7}/config/boot.rb +0 -0
  37. data/spec/support/5.1.7/config/cable.yml +10 -0
  38. data/spec/support/{rails_app → 5.1.7}/config/database.yml +0 -0
  39. data/spec/support/{rails_app → 5.1.7}/config/environment.rb +0 -0
  40. data/spec/support/5.1.7/config/environments/development.rb +47 -0
  41. data/spec/support/5.1.7/config/environments/production.rb +83 -0
  42. data/spec/support/5.1.7/config/environments/test.rb +42 -0
  43. data/spec/support/{rails_app → 5.1.7}/config/initializers/application_controller_renderer.rb +0 -0
  44. data/spec/support/{rails_app → 5.1.7}/config/initializers/backtrace_silencers.rb +0 -0
  45. data/spec/support/{rails_app → 5.1.7}/config/initializers/cors.rb +0 -0
  46. data/spec/support/{rails_app → 5.1.7}/config/initializers/filter_parameter_logging.rb +0 -0
  47. data/spec/support/{rails_app → 5.1.7}/config/initializers/inflections.rb +0 -0
  48. data/spec/support/{rails_app → 5.1.7}/config/initializers/mime_types.rb +0 -0
  49. data/spec/support/{rails_app → 5.1.7}/config/initializers/wrap_parameters.rb +0 -0
  50. data/spec/support/{rails_app → 5.1.7}/config/locales/en.yml +0 -0
  51. data/spec/support/5.1.7/config/puma.rb +56 -0
  52. data/spec/support/5.1.7/config/routes.rb +1 -0
  53. data/spec/support/5.1.7/config/secrets.yml +32 -0
  54. data/spec/support/5.1.7/config/spring.rb +6 -0
  55. data/spec/support/5.1.7/db/migrate +1 -0
  56. data/spec/support/{rails_app → 5.1.7}/db/schema.rb +7 -1
  57. data/spec/support/{rails_app → 5.1.7}/db/seeds.rb +0 -0
  58. data/spec/support/{rails_app/app/controllers/concerns → 5.1.7/lib/tasks}/.keep +0 -0
  59. data/spec/support/{rails_app/app/models/concerns → 5.1.7/log}/.keep +0 -0
  60. data/spec/support/{rails_app → 5.1.7}/public/robots.txt +0 -0
  61. data/spec/support/{rails_app/lib/tasks → 5.1.7/test/controllers}/.keep +0 -0
  62. data/spec/support/{rails_app/log → 5.1.7/test/fixtures}/.keep +0 -0
  63. data/spec/support/{rails_app/storage → 5.1.7/test/fixtures/files}/.keep +0 -0
  64. data/spec/support/{rails_app/test/controllers → 5.1.7/test/integration}/.keep +0 -0
  65. data/spec/support/{rails_app/test/fixtures → 5.1.7/test/mailers}/.keep +0 -0
  66. data/spec/support/{rails_app/test/fixtures/files → 5.1.7/test/models}/.keep +0 -0
  67. data/spec/support/5.1.7/test/test_helper.rb +10 -0
  68. data/spec/support/{rails_app/test/integration → 5.1.7/tmp}/.keep +0 -0
  69. data/spec/support/{rails_app/test/mailers → 5.1.7/vendor}/.keep +0 -0
  70. data/spec/support/{rails_app → 5.2.3}/Gemfile +13 -2
  71. data/spec/support/5.2.3/README.md +24 -0
  72. data/spec/support/5.2.3/Rakefile +6 -0
  73. data/spec/support/5.2.3/app/channels/application_cable/channel.rb +4 -0
  74. data/spec/support/5.2.3/app/channels/application_cable/connection.rb +4 -0
  75. data/spec/support/5.2.3/app/controllers +1 -0
  76. data/spec/support/5.2.3/app/jobs/application_job.rb +2 -0
  77. data/spec/support/5.2.3/app/mailers/application_mailer.rb +4 -0
  78. data/spec/support/5.2.3/app/models +1 -0
  79. data/spec/support/5.2.3/app/views/layouts/mailer.html.erb +13 -0
  80. data/spec/support/5.2.3/app/views/layouts/mailer.text.erb +1 -0
  81. data/spec/support/{rails_app → 5.2.3}/bin/bundle +0 -0
  82. data/spec/support/5.2.3/bin/rails +9 -0
  83. data/spec/support/5.2.3/bin/rake +9 -0
  84. data/spec/support/{rails_app → 5.2.3}/bin/setup +0 -0
  85. data/spec/support/5.2.3/bin/spring +17 -0
  86. data/spec/support/{rails_app → 5.2.3}/bin/update +0 -0
  87. data/spec/support/5.2.3/config.ru +5 -0
  88. data/spec/support/{rails_app → 5.2.3}/config/application.rb +0 -0
  89. data/spec/support/5.2.3/config/boot.rb +3 -0
  90. data/spec/support/{rails_app → 5.2.3}/config/cable.yml +0 -0
  91. data/spec/support/{rails_app → 5.2.3}/config/credentials.yml.enc +0 -0
  92. data/spec/support/5.2.3/config/database.yml +25 -0
  93. data/spec/support/5.2.3/config/environment.rb +5 -0
  94. data/spec/support/{rails_app → 5.2.3}/config/environments/development.rb +0 -0
  95. data/spec/support/{rails_app → 5.2.3}/config/environments/production.rb +0 -0
  96. data/spec/support/{rails_app → 5.2.3}/config/environments/test.rb +0 -0
  97. data/spec/support/5.2.3/config/initializers/application_controller_renderer.rb +8 -0
  98. data/spec/support/5.2.3/config/initializers/backtrace_silencers.rb +7 -0
  99. data/spec/support/5.2.3/config/initializers/cors.rb +16 -0
  100. data/spec/support/5.2.3/config/initializers/filter_parameter_logging.rb +4 -0
  101. data/spec/support/5.2.3/config/initializers/inflections.rb +16 -0
  102. data/spec/support/5.2.3/config/initializers/mime_types.rb +4 -0
  103. data/spec/support/5.2.3/config/initializers/wrap_parameters.rb +14 -0
  104. data/spec/support/5.2.3/config/locales/en.yml +33 -0
  105. data/spec/support/{rails_app → 5.2.3}/config/puma.rb +0 -0
  106. data/spec/support/5.2.3/config/routes.rb +1 -0
  107. data/spec/support/{rails_app → 5.2.3}/config/spring.rb +0 -0
  108. data/spec/support/{rails_app → 5.2.3}/config/storage.yml +0 -0
  109. data/spec/support/5.2.3/db/migrate +1 -0
  110. data/spec/support/5.2.3/db/schema.rb +47 -0
  111. data/spec/support/5.2.3/db/seeds.rb +7 -0
  112. data/spec/support/{rails_app/test/models → 5.2.3/lib/tasks}/.keep +0 -0
  113. data/spec/support/{rails_app/tmp → 5.2.3/log}/.keep +0 -0
  114. data/spec/support/5.2.3/public/robots.txt +1 -0
  115. data/spec/support/{rails_app/vendor → 5.2.3/storage}/.keep +0 -0
  116. data/spec/{test_data/expected_wizard_no_book.rbi → support/5.2.3/test/controllers/.keep} +0 -0
  117. data/spec/support/5.2.3/test/fixtures/.keep +0 -0
  118. data/spec/support/5.2.3/test/fixtures/files/.keep +0 -0
  119. data/spec/support/5.2.3/test/integration/.keep +0 -0
  120. data/spec/support/5.2.3/test/mailers/.keep +0 -0
  121. data/spec/support/5.2.3/test/models/.keep +0 -0
  122. data/spec/support/{rails_app → 5.2.3}/test/test_helper.rb +0 -0
  123. data/spec/support/5.2.3/tmp/.keep +0 -0
  124. data/spec/support/5.2.3/vendor/.keep +0 -0
  125. data/spec/support/{rails_app → rails_shared}/app/controllers/application_controller.rb +0 -0
  126. data/spec/support/rails_shared/app/controllers/concerns/.keep +0 -0
  127. data/spec/support/{rails_app → rails_shared}/app/models/application_record.rb +2 -0
  128. data/spec/support/rails_shared/app/models/potion.rb +5 -0
  129. data/spec/support/{rails_app → rails_shared}/app/models/spell_book.rb +0 -0
  130. data/spec/support/{rails_app → rails_shared}/app/models/wand.rb +0 -0
  131. data/spec/support/{rails_app → rails_shared}/app/models/wizard.rb +0 -0
  132. data/spec/support/{rails_app → rails_shared}/config/routes.rb +0 -0
  133. data/spec/support/{rails_app → rails_shared}/db/migrate/20190620001234_create_wizards.rb +1 -1
  134. data/spec/support/{rails_app → rails_shared}/db/migrate/20190620003037_create_wands.rb +1 -1
  135. data/spec/support/{rails_app → rails_shared}/db/migrate/20190620003739_create_spell_books.rb +1 -1
  136. data/spec/support/rails_shared/db/migrate/20190622000000_add_more_column_types_to_wands.rb +15 -0
  137. data/spec/test_data/5.1.7/expected_no_routes.rbi +11 -0
  138. data/spec/test_data/5.1.7/expected_potion.rbi +126 -0
  139. data/spec/test_data/5.1.7/expected_routes.rbi +13 -0
  140. data/spec/test_data/{models → 5.1.7}/expected_wand.rbi +66 -6
  141. data/spec/test_data/{models → 5.1.7}/expected_wizard.rbi +26 -8
  142. data/spec/test_data/5.1.7/expected_wizard_wo_spellbook.rbi +207 -0
  143. data/spec/test_data/5.2.3/expected_no_routes.rbi +11 -0
  144. data/spec/test_data/5.2.3/expected_potion.rbi +126 -0
  145. data/spec/test_data/{expected_routes.rbi → 5.2.3/expected_routes.rbi} +0 -0
  146. data/spec/test_data/5.2.3/expected_wand.rbi +246 -0
  147. data/spec/test_data/{models/expected_wizard_wo_spellbook.rbi → 5.2.3/expected_wizard.rbi} +26 -8
  148. data/spec/test_data/5.2.3/expected_wizard_wo_spellbook.rbi +207 -0
  149. metadata +277 -193
  150. data/Gemfile.lock +0 -176
  151. data/spec/support/rails_app/.ruby-version +0 -1
  152. data/spec/support/rails_app/Gemfile.lock +0 -139
  153. 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: ab8c01f01bcf1f1400adb25f52d0a6c35127a206
4
- data.tar.gz: ad481caa38ffc63d0ea5159a091ff91503c6f17b
3
+ metadata.gz: f46a2ecda33e62dbb1410943780827a098f5019d
4
+ data.tar.gz: fe0478758bf1882d6ef2749d9b1cdf56a96afebd
5
5
  SHA512:
6
- metadata.gz: 488ffc6eac8fec70207a86a71f13f034b55732df73ea9a3dc0aba4f694fa59eb36f98f52dee3f80267f9437484d9b165360a55b3f0033a81d8f5dd4f2bebb40e
7
- data.tar.gz: f82f7455bafb5a375ad65b6c0d725a15e2b4e0df53fbd3a3a69d65d7089571b29afbde8befb569ba4f8f1c66b708258bf6a91dcd5d5cae6bfbaa94f50c260abf
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
- # Gemfile.lock
46
- # .ruby-version
47
- # .ruby-gemset
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/rails_app/tmp/*
54
- /spec/support/rails_app/.bundle
53
+ /spec/support/**/tmp/*
54
+ /spec/support/**/.bundle
55
55
 
56
56
  # Ignore the default SQLite database.
57
- /spec/support/rails_app/db/*.sqlite3
58
- /spec/support/rails_app/db/*.sqlite3-journal
57
+ /spec/support/**/db/*.sqlite3
58
+ /spec/support/**/db/*.sqlite3-journal
59
59
 
60
60
  # Ignore all logfiles and tempfiles.
61
- /spec/support/rails_app/log/*
62
- /spec/support/rails_app/tmp/*
63
- !/spec/support/rails_app/log/.keep
64
- !/spec/support/rails_app/tmp/.keep
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/rails_app/storage/*
68
- !/spec/support/rails_app/storage/.keep
67
+ /spec/support/**/storage/*
68
+ !/spec/support/**/storage/.keep
69
69
 
70
- /spec/support/rails_app/node_modules
71
- /spec/support/rails_app/yarn-error.log
70
+ /spec/support/**/node_modules
71
+ /spec/support/**/yarn-error.log
72
72
 
73
- /spec/support/rails_app/public/assets
73
+ /spec/support/**/public/assets
74
74
  .byebug_history
75
75
 
76
76
  # Ignore master key for decrypting credentials and more.
77
- /spec/support/rails_app/config/master.key
77
+ /spec/support/**/config/master.key
78
+
@@ -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
- A set of tools to make the [Sorbet](https://sorbet.org) type checker work with Ruby on Rails seamlessly.
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. Install the gem and set up Sorbet, up to being able to run `srb tc`. Follow the steps [here](https://sorbet.org/docs/adopting).
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
- $ srb rbi sorbet-typed
23
+ bundle install
13
24
  ```
14
- 3. Generate RBI files for routes and models:
25
+
26
+ 3. Generate RBI files for your routes and models:
15
27
  ```sh
16
- $ rake rails_rbi:routes
17
- $ rake rails_rbi:models
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
- $ srb tc --suggest-typed --typed=strict --error-white-list=7022 --autocorrect
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 type-checkable now.
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 RBI
40
+ ### ActiveRecord
28
41
 
29
- There is an ActiveRecord RBI file that we vendor with this gem. Run `srb rbi sorbet-typed` to include the provided RBI file in your `sorbet/rbi_list`.
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 RBI
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
- $ rake rails_rbi:routes
48
+ rake rails_rbi:routes
36
49
  ```
37
- ### Models RBI
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
- $ rake rails_rbi:models
54
+ rake rails_rbi:models
42
55
  ```
43
56
  You can also regenerate RBI files for specific models:
44
57
  ```sh
45
- $ rake rails_rbi:models[ModelName,AnotherOne,...]
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
- RUBY_TO_SORBET_TYPE_MAPPING = {
5
- boolean: 'T::Boolean',
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 << draw_class_header
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, false)
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, true)
48
+ draw_method(method_name, method_obj, expected_sig)
55
49
  end
50
+ @buffer << draw_module_or_class_footer
56
51
 
57
- @buffer << draw_class_footer
58
-
59
- # <Model>::NamedScope is a fake module added so that when a method is defined
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 NamedScope module after the other classes
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 << "\n"
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 NamedScope
76
- draw_method(method_name, method_obj, expected_sig, false)
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 << draw_named_scope_footer
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, is_class_method)
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
- if !is_method_autogenerated?(method_obj)
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::Relation[T.untyped]" :
207
- "#{assoc_class}::Relation"
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 draw_class_header
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}::NamedScope
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}::NamedScope
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 draw_class_footer
267
- "end"
258
+ def draw_module_or_class_footer
259
+ <<~MESSAGE
260
+ end
261
+ MESSAGE
268
262
  end
269
263
 
270
- def draw_named_scope_header
264
+ def draw_module_header(name)
271
265
  <<~MESSAGE
272
- module #{@model_class.name}::NamedScope
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
- strict_type = RUBY_TO_SORBET_TYPE_MAPPING[column_def.type] || 'T.untyped'
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 is_method_autogenerated?(method_obj)
296
- # check if this method is autogenerated or overridden
297
- # Note: sometimes this is a module, sometimes it's an instance of a class
298
- return false unless method_obj.source_location.present? # BaseObject
299
-
300
- owner_name = method_obj.owner.to_s
301
- source_file = method_obj.source_location[0]
302
-
303
- (
304
- [
305
- "ActiveRecord::AttributeMethods::GeneratedAttributeMethods",
306
- "GeneratedAssociationMethods",
307
- "ActiveRecord::Querying",
308
- "ActiveRecord::AttributeMethods::PrimaryKey",
309
- ].any? { |k| owner_name.include?(k) } ||
310
- [
311
- "lib/active_record/enum.rb",
312
- "lib/active_record/scoping/named.rb",
313
- ].any? { |k| source_file.include?(k) }
314
- )
315
- end
316
-
317
- def matched_signature?(method_obj, generated_method_def)
318
- # use parameters reflection to find method arguments
319
- actual_params = method_obj.parameters
320
- expected_args = generated_method_def[:args] || []
321
- expected_params = expected_args.map { |arg| [arg[:arg_type], arg[:name]] }
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, is_class_method)
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 #{prefix}#{method_name}(#{param_def}); end
353
+ def #{method_name}(#{param_def}); end
365
354
  MESSAGE
366
355
  end
367
356
  end