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.
Files changed (204) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/README.md +3 -3
  4. data/lib/bundled_rbi/typed_enum.rbi +7 -0
  5. data/lib/sorbet-rails.rb +0 -2
  6. data/lib/sorbet-rails/active_record_rbi_formatter.rb +301 -0
  7. data/lib/sorbet-rails/config.rb +0 -1
  8. data/lib/sorbet-rails/dependent_gem_rbis/activerecord.rbi +11 -0
  9. data/lib/sorbet-rails/deprecation.rb +5 -0
  10. data/lib/sorbet-rails/gem_plugins/active_flag_plugin.rb +0 -1
  11. data/lib/sorbet-rails/gem_plugins/paperclip_plugin.rb +0 -1
  12. data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +36 -8
  13. data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +20 -9
  14. data/lib/sorbet-rails/model_plugins/active_record_enum.rb +0 -1
  15. data/lib/sorbet-rails/model_plugins/active_record_named_scope.rb +15 -6
  16. data/lib/sorbet-rails/model_plugins/active_record_querying.rb +34 -3
  17. data/lib/sorbet-rails/model_plugins/active_storage_methods.rb +1 -1
  18. data/lib/sorbet-rails/model_plugins/base.rb +10 -0
  19. data/lib/sorbet-rails/model_plugins/enumerable_collections.rb +0 -50
  20. data/lib/sorbet-rails/model_plugins/plugins.rb +0 -3
  21. data/lib/sorbet-rails/model_rbi_formatter.rb +6 -10
  22. data/lib/sorbet-rails/model_utils.rb +83 -36
  23. data/lib/sorbet-rails/rails_mixins/generated_url_helpers.rb +2 -3
  24. data/lib/sorbet-rails/railtie.rb +0 -2
  25. data/lib/sorbet-rails/tasks/rails_rbi.rake +32 -24
  26. data/sorbet-rails.gemspec +2 -2
  27. data/spec/active_record_rbi_formatter_spec.rb +24 -0
  28. data/spec/generators/rails-template.rb +90 -6
  29. data/spec/generators/sorbet_test_cases.rb +204 -122
  30. data/spec/model_rbi_formatter_spec.rb +1 -1
  31. data/spec/rails_helper.rb +14 -1
  32. data/spec/rake_rails_rbi_active_record_spec.rb +21 -0
  33. data/spec/rake_rails_rbi_models_spec.rb +7 -0
  34. data/spec/sorbet_spec.rb +12 -1
  35. data/spec/support/v5.0/Gemfile.lock +8 -8
  36. data/spec/support/v5.0/app/models/headmaster.rb +8 -0
  37. data/spec/support/v5.0/app/models/school.rb +2 -0
  38. data/spec/support/v5.0/app/models/spell.rb +5 -0
  39. data/spec/support/v5.0/app/models/spell_book.rb +5 -0
  40. data/spec/support/v5.0/app/models/subject.rb +5 -0
  41. data/spec/support/v5.0/app/models/wizard.rb +6 -1
  42. data/spec/support/v5.0/db/migrate/20190620000010_add_subject.rb +8 -0
  43. data/spec/support/v5.0/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
  44. data/spec/support/v5.0/db/migrate/20190620000012_add_spell.rb +8 -0
  45. data/spec/support/v5.0/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
  46. data/spec/support/v5.0/db/migrate/20190620000014_create_headmasters.rb +9 -0
  47. data/spec/support/v5.0/db/schema.rb +28 -1
  48. data/spec/support/v5.0/lib/mythical_rbi_plugin.rb +1 -1
  49. data/spec/support/v5.0/sorbet_test_cases.rb +204 -122
  50. data/spec/support/v5.1/Gemfile.lock +8 -8
  51. data/spec/support/v5.1/app/models/headmaster.rb +8 -0
  52. data/spec/support/v5.1/app/models/school.rb +2 -0
  53. data/spec/support/v5.1/app/models/spell.rb +5 -0
  54. data/spec/support/v5.1/app/models/spell_book.rb +5 -0
  55. data/spec/support/v5.1/app/models/subject.rb +5 -0
  56. data/spec/support/v5.1/app/models/wizard.rb +6 -1
  57. data/spec/support/v5.1/db/migrate/20190620000010_add_subject.rb +8 -0
  58. data/spec/support/v5.1/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
  59. data/spec/support/v5.1/db/migrate/20190620000012_add_spell.rb +8 -0
  60. data/spec/support/v5.1/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
  61. data/spec/support/v5.1/db/migrate/20190620000014_create_headmasters.rb +9 -0
  62. data/spec/support/v5.1/db/schema.rb +28 -1
  63. data/spec/support/v5.1/lib/mythical_rbi_plugin.rb +1 -1
  64. data/spec/support/v5.1/sorbet_test_cases.rb +204 -122
  65. data/spec/support/v5.2/Gemfile +1 -1
  66. data/spec/support/v5.2/Gemfile.lock +9 -9
  67. data/spec/support/v5.2/app/models/headmaster.rb +8 -0
  68. data/spec/support/v5.2/app/models/school.rb +2 -0
  69. data/spec/support/v5.2/app/models/spell.rb +5 -0
  70. data/spec/support/v5.2/app/models/spell_book.rb +5 -0
  71. data/spec/support/v5.2/app/models/subject.rb +5 -0
  72. data/spec/support/v5.2/app/models/wizard.rb +5 -0
  73. data/spec/support/v5.2/config/puma.rb +3 -0
  74. data/spec/support/v5.2/db/migrate/20190620000010_add_subject.rb +8 -0
  75. data/spec/support/v5.2/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
  76. data/spec/support/v5.2/db/migrate/20190620000012_add_spell.rb +8 -0
  77. data/spec/support/v5.2/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
  78. data/spec/support/v5.2/db/migrate/20190620000014_create_headmasters.rb +9 -0
  79. data/spec/support/v5.2/db/schema.rb +28 -1
  80. data/spec/support/v5.2/lib/mythical_rbi_plugin.rb +1 -1
  81. data/spec/support/v5.2/sorbet_test_cases.rb +204 -122
  82. data/spec/support/v6.0/Gemfile.lock +8 -8
  83. data/spec/support/v6.0/app/models/headmaster.rb +8 -0
  84. data/spec/support/v6.0/app/models/school.rb +2 -0
  85. data/spec/support/v6.0/app/models/spell.rb +5 -0
  86. data/spec/support/v6.0/app/models/spell_book.rb +5 -0
  87. data/spec/support/v6.0/app/models/subject.rb +5 -0
  88. data/spec/support/v6.0/app/models/wizard.rb +6 -1
  89. data/spec/support/v6.0/db/migrate/20190620000010_add_subject.rb +8 -0
  90. data/spec/support/v6.0/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
  91. data/spec/support/v6.0/db/migrate/20190620000012_add_spell.rb +8 -0
  92. data/spec/support/v6.0/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
  93. data/spec/support/v6.0/db/migrate/20190620000014_create_headmasters.rb +9 -0
  94. data/spec/support/v6.0/db/schema.rb +28 -1
  95. data/spec/support/v6.0/lib/mythical_rbi_plugin.rb +1 -1
  96. data/spec/support/v6.0/sorbet_test_cases.rb +204 -122
  97. data/spec/test_data/v5.0/expected_active_record_base.rbi +113 -0
  98. data/spec/test_data/v5.0/expected_active_record_relation.rbi +199 -0
  99. data/spec/test_data/v5.0/expected_habtm_subjects.rbi +660 -0
  100. data/spec/test_data/v5.0/expected_habtm_wizards.rbi +660 -0
  101. data/spec/test_data/v5.0/expected_headmaster.rbi +304 -0
  102. data/spec/test_data/v5.0/expected_internal_metadata.rbi +38 -401
  103. data/spec/test_data/v5.0/expected_potion.rbi +38 -401
  104. data/spec/test_data/v5.0/expected_robe.rbi +38 -403
  105. data/spec/test_data/v5.0/expected_schema_migration.rbi +38 -401
  106. data/spec/test_data/v5.0/expected_school.rbi +47 -401
  107. data/spec/test_data/v5.0/expected_spell.rbi +292 -0
  108. data/spec/test_data/v5.0/expected_spell/habtm_spell_books.rbi +295 -0
  109. data/spec/test_data/v5.0/expected_spell_book.rbi +86 -432
  110. data/spec/test_data/v5.0/expected_spell_book/habtm_spell_books.rbi +637 -0
  111. data/spec/test_data/v5.0/expected_spell_book/habtm_spells.rbi +295 -0
  112. data/spec/test_data/v5.0/expected_squib.rbi +122 -477
  113. data/spec/test_data/v5.0/expected_subject.rbi +292 -0
  114. data/spec/test_data/v5.0/expected_subject/habtm_wizards.rbi +295 -0
  115. data/spec/test_data/v5.0/expected_wand.rbi +75 -442
  116. data/spec/test_data/v5.0/expected_wizard.rbi +144 -498
  117. data/spec/test_data/v5.0/expected_wizard/habtm_subjects.rbi +295 -0
  118. data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +138 -498
  119. data/spec/test_data/v5.1/expected_active_record_base.rbi +113 -0
  120. data/spec/test_data/v5.1/expected_active_record_relation.rbi +178 -0
  121. data/spec/test_data/v5.1/expected_habtm_subjects.rbi +672 -0
  122. data/spec/test_data/v5.1/expected_habtm_wizards.rbi +672 -0
  123. data/spec/test_data/v5.1/expected_headmaster.rbi +310 -0
  124. data/spec/test_data/v5.1/expected_internal_metadata.rbi +38 -407
  125. data/spec/test_data/v5.1/expected_potion.rbi +38 -407
  126. data/spec/test_data/v5.1/expected_robe.rbi +38 -409
  127. data/spec/test_data/v5.1/expected_schema_migration.rbi +38 -407
  128. data/spec/test_data/v5.1/expected_school.rbi +47 -407
  129. data/spec/test_data/v5.1/expected_spell.rbi +298 -0
  130. data/spec/test_data/v5.1/expected_spell/habtm_spell_books.rbi +301 -0
  131. data/spec/test_data/v5.1/expected_spell_book.rbi +86 -438
  132. data/spec/test_data/v5.1/expected_spell_book/habtm_spell_books.rbi +649 -0
  133. data/spec/test_data/v5.1/expected_spell_book/habtm_spells.rbi +301 -0
  134. data/spec/test_data/v5.1/expected_squib.rbi +123 -484
  135. data/spec/test_data/v5.1/expected_subject.rbi +298 -0
  136. data/spec/test_data/v5.1/expected_subject/habtm_wizards.rbi +301 -0
  137. data/spec/test_data/v5.1/expected_wand.rbi +75 -448
  138. data/spec/test_data/v5.1/expected_wizard.rbi +145 -505
  139. data/spec/test_data/v5.1/expected_wizard/habtm_subjects.rbi +301 -0
  140. data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +139 -505
  141. data/spec/test_data/v5.2/expected_active_record_base.rbi +113 -0
  142. data/spec/test_data/v5.2/expected_active_record_relation.rbi +175 -0
  143. data/spec/test_data/v5.2/expected_attachment.rbi +38 -407
  144. data/spec/test_data/v5.2/expected_blob.rbi +60 -426
  145. data/spec/test_data/v5.2/expected_habtm_subjects.rbi +672 -0
  146. data/spec/test_data/v5.2/expected_habtm_wizards.rbi +672 -0
  147. data/spec/test_data/v5.2/expected_headmaster.rbi +310 -0
  148. data/spec/test_data/v5.2/expected_internal_metadata.rbi +38 -407
  149. data/spec/test_data/v5.2/expected_potion.rbi +38 -407
  150. data/spec/test_data/v5.2/expected_robe.rbi +38 -409
  151. data/spec/test_data/v5.2/expected_schema_migration.rbi +38 -407
  152. data/spec/test_data/v5.2/expected_school.rbi +47 -407
  153. data/spec/test_data/v5.2/expected_spell.rbi +298 -0
  154. data/spec/test_data/v5.2/expected_spell/habtm_spell_books.rbi +301 -0
  155. data/spec/test_data/v5.2/expected_spell_book.rbi +86 -438
  156. data/spec/test_data/v5.2/expected_spell_book/habtm_spell_books.rbi +649 -0
  157. data/spec/test_data/v5.2/expected_spell_book/habtm_spells.rbi +301 -0
  158. data/spec/test_data/v5.2/expected_squib.rbi +133 -488
  159. data/spec/test_data/v5.2/expected_subject.rbi +298 -0
  160. data/spec/test_data/v5.2/expected_subject/habtm_wizards.rbi +301 -0
  161. data/spec/test_data/v5.2/expected_wand.rbi +75 -448
  162. data/spec/test_data/v5.2/expected_wizard.rbi +155 -509
  163. data/spec/test_data/v5.2/expected_wizard/habtm_subjects.rbi +301 -0
  164. data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +149 -509
  165. data/spec/test_data/v6.0/expected_active_record_base.rbi +113 -0
  166. data/spec/test_data/v6.0/expected_active_record_relation.rbi +175 -0
  167. data/spec/test_data/v6.0/expected_attachment.rbi +38 -431
  168. data/spec/test_data/v6.0/expected_blob.rbi +60 -450
  169. data/spec/test_data/v6.0/expected_habtm_subjects.rbi +720 -0
  170. data/spec/test_data/v6.0/expected_habtm_wizards.rbi +720 -0
  171. data/spec/test_data/v6.0/expected_headmaster.rbi +334 -0
  172. data/spec/test_data/v6.0/expected_internal_metadata.rbi +38 -431
  173. data/spec/test_data/v6.0/expected_potion.rbi +38 -431
  174. data/spec/test_data/v6.0/expected_robe.rbi +38 -433
  175. data/spec/test_data/v6.0/expected_routes.rbi +14 -7
  176. data/spec/test_data/v6.0/expected_schema_migration.rbi +38 -431
  177. data/spec/test_data/v6.0/expected_school.rbi +47 -431
  178. data/spec/test_data/v6.0/expected_spell.rbi +322 -0
  179. data/spec/test_data/v6.0/expected_spell/habtm_spell_books.rbi +325 -0
  180. data/spec/test_data/v6.0/expected_spell_book.rbi +98 -474
  181. data/spec/test_data/v6.0/expected_spell_book/habtm_spell_books.rbi +697 -0
  182. data/spec/test_data/v6.0/expected_spell_book/habtm_spells.rbi +325 -0
  183. data/spec/test_data/v6.0/expected_squib.rbi +162 -541
  184. data/spec/test_data/v6.0/expected_subject.rbi +322 -0
  185. data/spec/test_data/v6.0/expected_subject/habtm_wizards.rbi +325 -0
  186. data/spec/test_data/v6.0/expected_wand.rbi +91 -488
  187. data/spec/test_data/v6.0/expected_wizard.rbi +184 -562
  188. data/spec/test_data/v6.0/expected_wizard/habtm_subjects.rbi +325 -0
  189. data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +178 -562
  190. metadata +170 -24
  191. data/lib/bundled_rbi/active_record_base.rbi +0 -83
  192. data/lib/bundled_rbi/active_record_relation.rbi +0 -122
  193. data/lib/bundled_rbi/parameters.rbi +0 -28
  194. data/lib/sorbet-rails/custom_types/boolean_string.rb +0 -32
  195. data/lib/sorbet-rails/custom_types/integer_string.rb +0 -35
  196. data/lib/sorbet-rails/model_plugins/active_record_finder_methods.rb +0 -131
  197. data/lib/sorbet-rails/rails_mixins/custom_params_methods.rb +0 -46
  198. data/spec/boolean_string_spec.rb +0 -59
  199. data/spec/custom_params_methods_spec.rb +0 -138
  200. data/spec/integer_string_spec.rb +0 -46
  201. data/spec/support/v5.0/typed-override.yaml +0 -2
  202. data/spec/support/v5.1/typed-override.yaml +0 -2
  203. data/spec/support/v5.2/typed-override.yaml +0 -2
  204. 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: a7e0b556a5c7db33a8fa24c494fa70265802a360eb842545b6124d54ade20e47
4
- data.tar.gz: 5f26a6773d2769e1dfc523f4202b179ded8deaebb65e05ade61291f03153f164
3
+ metadata.gz: 573cda715fb4ceb9881c61dce1f346499a60202ac181548727d7519f8f1b24fe
4
+ data.tar.gz: ba219d4d0ec79076e4a93cf2b6c0eb40272489fb4b71d42eea881c6c7c124b6e
5
5
  SHA512:
6
- metadata.gz: cc61969e6954e727113e9e74ba48925c15ee07b940fbe5d3a711d62270f90ba06ce9c36373420a3cf02c9a52bda3256ef63d9bea6bf2bcd580dfa592faba068c
7
- data.tar.gz: 8c0bb6e747de6ac12c77b326b01d941f3f24fdd6444fca05d573ff26c20a2d0d679bbcd713adb37f66919781446137d3f0fdd21bb39b36241c6e1d6a98ac1de6
6
+ metadata.gz: e6d81c40b7c86667834b5063637e9ed29204fda6494aaeccd59781ae481849d7e90b4f5edf678b6727197e6f194c3b06bc87c276e7801013343f2e698c904e3e
7
+ data.tar.gz: 11b034a0b6691ae3d9baf8a2c285b3277c99aa25d280eba8bf12bc78b5a443a4d5d502e814edf1eb576c1bbc108301cfcc0b240cff354a03163497e109ca403c
@@ -24,6 +24,8 @@ matrix:
24
24
  env: RAILS_VERSION=6.0
25
25
  allow_failures:
26
26
  - rvm: ruby-head
27
+ - rvm: 2.7
28
+ env: RAILS_VERSION=6.0
27
29
  before_install:
28
30
  - gem install bundler -v 2.0.1 --no-doc
29
31
  - gem install bundler -v 1.17.3 --no-doc
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`](#enums)
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
- #### Enums
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
 
@@ -0,0 +1,7 @@
1
+ # typed: strong
2
+ module ActiveRecord::Enum
3
+
4
+ sig { params(definitions: T::Hash[Symbol, T.untyped]).void }
5
+ def typed_enum(definitions); end
6
+
7
+ end
@@ -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
@@ -47,7 +47,6 @@ module SorbetRails
47
47
  :active_relation_where_not,
48
48
  :active_record_attribute,
49
49
  :active_record_assoc,
50
- :active_record_finder_methods,
51
50
  :custom_finder_methods,
52
51
  :enumerable_collections,
53
52
  ]
@@ -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
@@ -0,0 +1,5 @@
1
+ require 'active_support/deprecation'
2
+
3
+ module SorbetRails
4
+ TypeAssertDeprecation = ActiveSupport::Deprecation.new('0.7', 'SorbetRails')
5
+ 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
- column_def = @columns_hash[reflection.foreign_key.to_s]
69
- db_required_config = column_def.present? && !column_def.null
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
- puts "Note: belongs_to association #{reflection.name} is specified as not-null at the
77
- DB level but **optional** at the application level.\n Add a constraint at the app level
78
- (using `optional: false`) as a validation hint to Rails.".squish!
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
- polymorphic_assoc?(reflection) || !@available_classes.include?(reflection.klass.name)
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)) }