sorbet-rails 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CONTRIBUTING.md +2 -2
  4. data/README.md +22 -1
  5. data/lib/sorbet-rails/actionmailer.rbi +7 -0
  6. data/lib/sorbet-rails/activerecord.rbi +3 -0
  7. data/lib/sorbet-rails/config.rb +5 -0
  8. data/lib/sorbet-rails/helper_rbi_formatter.rb +3 -0
  9. data/lib/sorbet-rails/mailer_rbi_formatter.rb +38 -0
  10. data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +26 -6
  11. data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +4 -5
  12. data/lib/sorbet-rails/model_plugins/active_record_enum.rb +37 -3
  13. data/lib/sorbet-rails/model_plugins/active_record_named_scope.rb +2 -0
  14. data/lib/sorbet-rails/model_plugins/active_record_overrides.rb +27 -0
  15. data/lib/sorbet-rails/model_plugins/active_storage_methods.rb +60 -0
  16. data/lib/sorbet-rails/model_plugins/plugins.rb +4 -0
  17. data/lib/sorbet-rails/sorbet_utils.rb +68 -0
  18. data/lib/sorbet-rails/tasks/rails_rbi.rake +20 -0
  19. data/lib/sorbet-rails/utils.rb +5 -0
  20. data/sorbet-rails.gemspec +1 -1
  21. data/spec/generators/rails-template.rb +145 -16
  22. data/spec/generators/sorbet_test_cases.rb +11 -0
  23. data/spec/helper_rbi_formatter_spec.rb +19 -0
  24. data/spec/mailer_rbi_formatter_spec.rb +13 -0
  25. data/spec/model_rbi_formatter_spec.rb +12 -2
  26. data/spec/rails_helper.rb +20 -0
  27. data/spec/rake_rails_rbi_mailers_spec.rb +21 -0
  28. data/spec/rake_rails_rbi_models_spec.rb +1 -21
  29. data/spec/sorbet_spec.rb +13 -0
  30. data/spec/sorbet_utils_spec.rb +126 -0
  31. data/spec/support/v4.2/Gemfile.lock +5 -5
  32. data/spec/support/v4.2/app/mailers/application_mailer.rb +3 -0
  33. data/spec/support/v4.2/app/mailers/daily_prophet_mailer.rb +9 -0
  34. data/spec/support/v4.2/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
  35. data/spec/support/v4.2/app/models/wizard.rb +14 -0
  36. data/spec/support/v4.2/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
  37. data/spec/support/v4.2/db/schema.rb +2 -1
  38. data/spec/support/v4.2/sorbet_test_cases.rb +11 -0
  39. data/spec/support/v5.0/Gemfile.lock +6 -6
  40. data/spec/support/v5.0/app/mailers/daily_prophet_mailer.rb +9 -0
  41. data/spec/support/v5.0/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
  42. data/spec/support/v5.0/app/models/wizard.rb +33 -0
  43. data/spec/support/v5.0/config/initializers/new_framework_defaults.rb +1 -1
  44. data/spec/support/v5.0/db/migrate/20190620000001_create_wizards.rb +1 -1
  45. data/spec/support/v5.0/db/migrate/20190620000002_create_wands.rb +1 -1
  46. data/spec/support/v5.0/db/migrate/20190620000003_create_spell_books.rb +1 -1
  47. data/spec/support/v5.0/db/migrate/20190620000004_add_more_column_types_to_wands.rb +1 -1
  48. data/spec/support/v5.0/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
  49. data/spec/support/v5.0/db/migrate/20190620000006_add_more_enums_to_wizard.rb +9 -0
  50. data/spec/support/v5.0/db/schema.rb +9 -4
  51. data/spec/support/v5.0/sorbet_test_cases.rb +11 -0
  52. data/spec/support/v5.1/Gemfile.lock +6 -6
  53. data/spec/support/v5.1/app/mailers/daily_prophet_mailer.rb +9 -0
  54. data/spec/support/v5.1/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
  55. data/spec/support/v5.1/app/models/wizard.rb +33 -0
  56. data/spec/support/v5.1/db/migrate/20190620000001_create_wizards.rb +1 -1
  57. data/spec/support/v5.1/db/migrate/20190620000002_create_wands.rb +1 -1
  58. data/spec/support/v5.1/db/migrate/20190620000003_create_spell_books.rb +1 -1
  59. data/spec/support/v5.1/db/migrate/20190620000004_add_more_column_types_to_wands.rb +1 -1
  60. data/spec/support/v5.1/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
  61. data/spec/support/v5.1/db/migrate/20190620000006_add_more_enums_to_wizard.rb +9 -0
  62. data/spec/support/v5.1/db/schema.rb +7 -2
  63. data/spec/support/v5.1/sorbet_test_cases.rb +11 -0
  64. data/spec/support/v5.2/Gemfile.lock +6 -6
  65. data/spec/support/v5.2/app/mailers/daily_prophet_mailer.rb +9 -0
  66. data/spec/support/v5.2/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
  67. data/spec/support/v5.2/app/models/wizard.rb +34 -0
  68. data/spec/support/v5.2/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
  69. data/spec/support/v5.2/db/migrate/20190620000006_add_more_enums_to_wizard.rb +9 -0
  70. data/spec/support/v5.2/db/schema.rb +7 -2
  71. data/spec/support/v5.2/sorbet_test_cases.rb +11 -0
  72. data/spec/support/v6.0/Gemfile.lock +6 -6
  73. data/spec/support/v6.0/app/mailers/daily_prophet_mailer.rb +9 -0
  74. data/spec/support/v6.0/app/mailers/hogwarts_acceptance_mailer.rb +13 -0
  75. data/spec/support/v6.0/app/models/wizard.rb +35 -1
  76. data/spec/support/v6.0/db/migrate/20190620000005_add_broom_to_wizard.rb +6 -0
  77. data/spec/support/v6.0/db/migrate/20190620000006_add_more_enums_to_wizard.rb +9 -0
  78. data/spec/support/v6.0/db/schema.rb +6 -1
  79. data/spec/support/v6.0/sorbet_test_cases.rb +11 -0
  80. data/spec/test_data/v4.2/expected_application_mailer.rbi +5 -0
  81. data/spec/test_data/v4.2/expected_daily_prophet_mailer.rbi +7 -0
  82. data/spec/test_data/v4.2/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
  83. data/spec/test_data/v4.2/expected_hogwarts_acceptance_mailer.rbi +10 -0
  84. data/spec/test_data/v4.2/expected_srb_tc_output.txt +57 -1
  85. data/spec/test_data/v4.2/expected_wizard.rbi +69 -0
  86. data/spec/test_data/v4.2/expected_wizard_wo_spellbook.rbi +69 -0
  87. data/spec/test_data/v5.0/expected_application_mailer.rbi +5 -0
  88. data/spec/test_data/v5.0/expected_daily_prophet_mailer.rbi +7 -0
  89. data/spec/test_data/v5.0/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
  90. data/spec/test_data/v5.0/expected_hogwarts_acceptance_mailer.rbi +10 -0
  91. data/spec/test_data/v5.0/expected_spell_book.rbi +2 -2
  92. data/spec/test_data/v5.0/expected_wizard.rbi +294 -0
  93. data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +294 -0
  94. data/spec/test_data/v5.1/expected_application_mailer.rbi +5 -0
  95. data/spec/test_data/v5.1/expected_daily_prophet_mailer.rbi +7 -0
  96. data/spec/test_data/v5.1/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
  97. data/spec/test_data/v5.1/expected_hogwarts_acceptance_mailer.rbi +10 -0
  98. data/spec/test_data/v5.1/expected_spell_book.rbi +2 -2
  99. data/spec/test_data/v5.1/expected_wizard.rbi +294 -0
  100. data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +294 -0
  101. data/spec/test_data/v5.2-no-sorbet/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
  102. data/spec/test_data/v5.2/expected_application_mailer.rbi +5 -0
  103. data/spec/test_data/v5.2/expected_attachment.rbi +4 -4
  104. data/spec/test_data/v5.2/expected_daily_prophet_mailer.rbi +7 -0
  105. data/spec/test_data/v5.2/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
  106. data/spec/test_data/v5.2/expected_hogwarts_acceptance_mailer.rbi +10 -0
  107. data/spec/test_data/v5.2/expected_spell_book.rbi +2 -2
  108. data/spec/test_data/v5.2/expected_wizard.rbi +342 -0
  109. data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +342 -0
  110. data/spec/test_data/v6.0/expected_application_mailer.rbi +5 -0
  111. data/spec/test_data/v6.0/expected_attachment.rbi +4 -4
  112. data/spec/test_data/v6.0/expected_blob.rbi +28 -22
  113. data/spec/test_data/v6.0/expected_daily_prophet_mailer.rbi +7 -0
  114. data/spec/test_data/v6.0/expected_helpers_with_application_and_devise_helpers.rbi +29 -0
  115. data/spec/test_data/v6.0/expected_hogwarts_acceptance_mailer.rbi +10 -0
  116. data/spec/test_data/v6.0/expected_spell_book.rbi +2 -2
  117. data/spec/test_data/v6.0/expected_wizard.rbi +526 -16
  118. data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +526 -16
  119. metadata +94 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3878883de9a212a28dbbd5f11aa6a1d6a0f58fd4840b26d958942b90b4bf802d
4
- data.tar.gz: 3367e7a6ad7519c10c3a160372b505a605ccefd2e4437ae86b275b1b01c5df4c
3
+ metadata.gz: 6b43d7a27b379e20892375b314afd08e4897ad6e0b20167b1a366de3121d0470
4
+ data.tar.gz: f6817dea5503132acc030da42ce68149675192c98536b8c795d8705a0e42649d
5
5
  SHA512:
6
- metadata.gz: 76b1d238246210fe8d3735dbf216c0cb89529550b9364204571d9d4aed878f42c3a998bb48eaeaa8eb156c2cca758cf4535fc95ae445ef2c6c192b58dbc3bdd0
7
- data.tar.gz: 97979cd91324b862e3ae0c0f9c53bd443613b1dfa0b8701680c0055d382057a6cdf45110183a43b19714b93b4d2ddedd9f7ba42ee4192f3d9c6597c8b4ddf45f
6
+ metadata.gz: 15251522124262b9c7aab57915d2cc5bc18e0807ccf27e2ea47e0d17173473a5d8a5a09ac4ea41e899de47a7972456ae060ef981e811e5f22095309c79d89b27
7
+ data.tar.gz: 7178e09505141a408617d2513f9da84ee8868edf5c808c3bca0ba293dbec4bb3cbbbe1362ad8b0af2f75f20862de4f95b67ddf6b1365a6d10111598bd9779b75
data/.gitignore CHANGED
@@ -94,3 +94,4 @@ build-iPhoneSimulator/
94
94
  *.swk
95
95
 
96
96
  lib/sorbet/rbi/hidden-definitions/errors.txt
97
+ .vscode
data/CONTRIBUTING.md CHANGED
@@ -124,8 +124,8 @@ copied into each app with `cp`.
124
124
  The `rails-template.rb` file uses the
125
125
  [Rails Application Template](https://guides.rubyonrails.org/rails_application_templates.html)
126
126
  functionality included in Rails. You can then regenerate each Rails app from
127
- the same file using `bundle _1.17.3_ exec rake update_spec:v4.2`, `bundle exec rake update_spec:v5.0`,
128
- `bundle exec rake update_spec:v5.1`, etc. (or just `bundle exec rake update_spec:v5_plus`,
127
+ the same file using `bundle _1.17.3_ exec rake update_spec:v4_2`, `bundle exec rake update_spec:v5_0`,
128
+ `bundle exec rake update_spec:v5_1`, etc. (or just `bundle exec rake update_spec:v5_plus`,
129
129
  though this excludes regenerating 4.2 because 4.2 blocks usage of Bundler 2.x).
130
130
 
131
131
  #### Expected Output
data/README.md CHANGED
@@ -30,6 +30,7 @@ gem 'sorbet-rails'
30
30
  ❯ rake rails_rbi:routes
31
31
  ❯ rake rails_rbi:models
32
32
  ❯ rake rails_rbi:helpers
33
+ ❯ rake rails_rbi:mailers
33
34
 
34
35
  # or run them all at once
35
36
  ❯ rake rails_rbi:all
@@ -76,12 +77,32 @@ It is possible to add custom RBI generation logic for your custom module or gems
76
77
 
77
78
  ### Helpers
78
79
 
79
- This Rake task generates a `helpers.rbi` file that includes a basic module definition which includes the `Kernel` module, to allow for some basic Ruby methods to be used in helpers without Sorbet complaining.
80
+ This Rake task generates a `helpers.rbi` file that includes a basic module definition which includes the `Kernel` module and `ActionView::Helpers`, to allow for some basic Ruby methods to be used in helpers without Sorbet complaining.
80
81
 
81
82
  ```sh
82
83
  ❯ rake rails_rbi:helpers
83
84
  ```
84
85
 
86
+ If you have additional modules that are included in all your helpers and you want `helpers.rbi` to reflect this, you can configure it:
87
+
88
+ ```ruby
89
+ # -- config/initializers/sorbet_rails.rb
90
+ SorbetRails.configure do |config|
91
+ config.extra_helper_includes = ['ApplicationHelper', 'Devise::Controllers::Helpers']
92
+ end
93
+
94
+ ### Mailers
95
+
96
+ This Rake task generates RBI files for all mailer classes in the Rails application (all descendants of `ActionMailer::Base`)
97
+ ```sh
98
+ ❯ rake rails_rbi:mailers
99
+ ```
100
+
101
+ Since mailing action methods is based on instance methods defined in a mailer class, the signature of a mailing action method will be dependent on the signature the instance method has
102
+ - If there is a (sorbet) sig written for the instance method, it generates a matching sig for the mailing action method
103
+ - If not, all the params in the mailing action method will be T.untyped.
104
+ - For return type though, the mailing action method will return `ActionMailer::MessageDelivery` instead of the return type of the instance method.
105
+
85
106
  ## Tips & Tricks
86
107
 
87
108
  ### Overriding generated signatures
@@ -0,0 +1,7 @@
1
+ # typed: strong
2
+ class ActionMailer::Base
3
+ extend T::Sig
4
+
5
+ sig { returns(T::Array[Symbol]) }
6
+ def self.action_methods; end
7
+ end
@@ -20,6 +20,9 @@ class ActiveRecord::Base < Object
20
20
 
21
21
  sig { returns(T::Boolean) }
22
22
  def self.abstract_class?; end
23
+
24
+ sig { returns(T::Hash[String, T.untyped]) }
25
+ def self.attachment_reflections; end
23
26
  end
24
27
 
25
28
  class ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter;
@@ -34,6 +34,9 @@ module SorbetRails
34
34
  sig { returns(T::Array[Symbol]) }
35
35
  attr_accessor :enabled_model_plugins
36
36
 
37
+ sig { returns(T::Array[String]) }
38
+ attr_accessor :extra_helper_includes
39
+
37
40
  sig { void }
38
41
  def initialize
39
42
  @enabled_gem_plugins = []
@@ -48,6 +51,8 @@ module SorbetRails
48
51
  :custom_finder_methods,
49
52
  :enumerable_collections,
50
53
  ]
54
+ @enabled_model_plugins << :active_storage_methods if defined?(T.unsafe(ActiveStorage))
55
+ @extra_helper_includes = []
51
56
  end
52
57
 
53
58
  sig { returns(T::Array[Symbol]) }
@@ -25,6 +25,9 @@ class SorbetRails::HelperRbiFormatter
25
25
  @parlour.root.create_module(helper.to_s) do |mod|
26
26
  mod.create_include('Kernel')
27
27
  mod.create_include('ActionView::Helpers')
28
+ ::SorbetRails.config.extra_helper_includes.each do |extra_helper|
29
+ mod.create_include(extra_helper) unless extra_helper == helper.to_s
30
+ end
28
31
  end
29
32
  end
30
33
 
@@ -0,0 +1,38 @@
1
+ # typed: strict
2
+ require('parlour')
3
+ require('sorbet-rails/sorbet_utils.rb')
4
+
5
+ class SorbetRails::MailerRbiFormatter
6
+ extend T::Sig
7
+
8
+ sig { params(mailer_class: T.class_of(ActionMailer::Base)).void }
9
+ def initialize(mailer_class)
10
+ @mailer_class = T.let(mailer_class, T.class_of(ActionMailer::Base))
11
+ @parlour = T.let(Parlour::RbiGenerator.new, Parlour::RbiGenerator)
12
+ end
13
+
14
+ sig {returns(String)}
15
+ def generate_rbi
16
+ puts "-- Generate sigs for mailer #{@mailer_class.name} --"
17
+
18
+ @parlour.root.add_comments([
19
+ 'This is an autogenerated file for Rails helpers.',
20
+ 'Please rerun rake rails_rbi:mailers to regenerate.'
21
+ ])
22
+
23
+ @parlour.root.create_class(@mailer_class.name) do |mailer_rbi|
24
+ @mailer_class.action_methods.to_a.sort.each do |mailer_method|
25
+ method_def = @mailer_class.instance_method(mailer_method)
26
+ parameters = SorbetRails::SorbetUtils.parameters_from_method_def(method_def)
27
+ mailer_rbi.create_method(
28
+ mailer_method,
29
+ parameters: parameters,
30
+ return_type: 'ActionMailer::MessageDelivery',
31
+ class_method: true,
32
+ )
33
+ end
34
+ end
35
+
36
+ @parlour.rbi + "\n"
37
+ end
38
+ end
@@ -1,7 +1,7 @@
1
1
  # typed: true
2
2
  require ('sorbet-rails/model_plugins/base')
3
3
  class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::Base
4
- sig {params(model_class: T.class_of(ActiveRecord::Base), available_classes: T::Set[String]).void}
4
+ sig { params(model_class: T.class_of(ActiveRecord::Base), available_classes: T::Set[String]).void }
5
5
  def initialize(model_class, available_classes)
6
6
  super
7
7
  @columns_hash = @model_class.table_exists? ? @model_class.columns_hash : {}
@@ -25,16 +25,29 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
25
25
  end
26
26
  end
27
27
 
28
+ sig do
29
+ params(
30
+ assoc_module_rbi: T.untyped,
31
+ assoc_name: T.untyped,
32
+ reflection: T.untyped
33
+ ).void
34
+ end
28
35
  def populate_single_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection)
29
36
  # TODO allow people to specify the possible values of polymorphic associations
30
37
  assoc_class = assoc_should_be_untyped?(reflection) ? "T.untyped" : "::#{reflection.klass.name}"
31
38
  assoc_type = "T.nilable(#{assoc_class})"
32
39
  if reflection.belongs_to?
33
- # if this is a belongs_to connection, we may be able to detect whether
34
- # this field is required & use a stronger type
35
40
  column_def = @columns_hash[reflection.foreign_key.to_s]
36
- if column_def
37
- assoc_type = assoc_class if !column_def.null
41
+ if ENV["RAILS_VERSION"] == "4.2"
42
+ # Before Rails 5, belongs_to relations were nilable by default
43
+ # if this is a belongs_to connection, we may be able to detect whether
44
+ # this field is required & use a stronger type
45
+ if column_def
46
+ assoc_type = assoc_class if !column_def.null
47
+ end
48
+ else
49
+ # In Rails 5 and later, belongs_to are required unless specified to be optional
50
+ assoc_type = assoc_class if !reflection.options[:optional]
38
51
  end
39
52
  end
40
53
 
@@ -51,6 +64,13 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
51
64
  )
52
65
  end
53
66
 
67
+ sig do
68
+ params(
69
+ assoc_module_rbi: T.untyped,
70
+ assoc_name: T.untyped,
71
+ reflection: T.untyped
72
+ ).void
73
+ end
54
74
  def populate_collection_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection)
55
75
  # TODO allow people to specify the possible values of polymorphic associations
56
76
  assoc_class = assoc_should_be_untyped?(reflection) ? "T.untyped" : "::#{reflection.klass.name}"
@@ -88,4 +108,4 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
88
108
  polymorphic_assoc?(reflection.source_reflection) :
89
109
  reflection.polymorphic?
90
110
  end
91
- end
111
+ end
@@ -55,7 +55,7 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
55
55
  end
56
56
  end
57
57
 
58
- sig {params(column_def: T.untyped).returns(T.any(String, Class))}
58
+ sig { params(column_def: T.untyped).returns(T.any(String, Class)) }
59
59
  def type_for_column_def(column_def)
60
60
  cast_type = ActiveRecord::Base.connection.respond_to?(:lookup_cast_type_from_column) ?
61
61
  ActiveRecord::Base.connection.lookup_cast_type_from_column(column_def) :
@@ -69,13 +69,12 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
69
69
  column_def.null ? "T.nilable(#{strict_type})" : strict_type
70
70
  end
71
71
 
72
- sig {
72
+ sig do
73
73
  params(
74
74
  # in v4.2, datetime can be TimeZoneConverter
75
75
  klass: T.any(Object, ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter)
76
- ).
77
- returns(T.any(String, Class))
78
- }
76
+ ).returns(T.any(String, Class))
77
+ end
79
78
  def active_record_type_to_sorbet_type(klass)
80
79
  case klass
81
80
  when ActiveRecord::Type::Boolean
@@ -1,5 +1,6 @@
1
1
  # typed: strict
2
2
  require ('sorbet-rails/model_plugins/base')
3
+ require("sorbet-rails/utils")
3
4
  class SorbetRails::ModelPlugins::ActiveRecordEnum < SorbetRails::ModelPlugins::Base
4
5
 
5
6
  sig { implementation.params(root: Parlour::RbiGenerator::Namespace).void }
@@ -13,20 +14,53 @@ class SorbetRails::ModelPlugins::ActiveRecordEnum < SorbetRails::ModelPlugins::B
13
14
  model_class_rbi = root.create_class(self.model_class_name)
14
15
  model_class_rbi.create_include(enum_module_name)
15
16
 
17
+ enum_calls = ActiveRecordOverrides.instance.enum_calls[self.model_class_name]
18
+
16
19
  # TODO: add any method for signature verification?
17
20
  model_class.defined_enums.sort.each do |enum_name, enum_hash|
21
+ value_type = enum_hash.values.map { |v| v.is_a?(Integer) ? 'Integer' : v.class.name }.uniq
22
+
23
+ return_type = if value_type.length == 1
24
+ "T::Hash[T.any(String, Symbol), #{value_type.first}]"
25
+ else
26
+ "T::Hash[T.any(String, Symbol), T.any(#{value_type.join(", ")})]"
27
+ end
28
+
18
29
  model_class_rbi.create_method(
19
30
  enum_name.pluralize,
20
- return_type: "T::Hash[T.any(String, Symbol), Integer]",
31
+ return_type: return_type,
21
32
  class_method: true,
22
33
  )
34
+
35
+ enum_call = enum_calls.find {|call| call.has_key?(enum_name.to_sym)}
36
+
37
+ enum_prefix = enum_call[:_prefix]
38
+ prefix =
39
+ if enum_prefix == true
40
+ "#{enum_name}_"
41
+ elsif enum_prefix
42
+ "#{enum_prefix}_"
43
+ else
44
+ ''
45
+ end
46
+ enum_suffix = enum_call[:_suffix]
47
+ suffix =
48
+ if enum_suffix == true
49
+ "_#{enum_name}"
50
+ elsif enum_suffix
51
+ "_#{enum_suffix}"
52
+ else
53
+ ''
54
+ end
55
+
23
56
  enum_hash.keys.each do |enum_val|
57
+ next unless SorbetRails::Utils.valid_method_name?(enum_val.to_s)
24
58
  enum_module_rbi.create_method(
25
- "#{enum_val}?",
59
+ "#{prefix}#{enum_val}#{suffix}?",
26
60
  return_type: "T::Boolean",
27
61
  )
28
62
  enum_module_rbi.create_method(
29
- "#{enum_val}!",
63
+ "#{prefix}#{enum_val}#{suffix}!",
30
64
  return_type: nil, # void
31
65
  )
32
66
  end
@@ -1,5 +1,6 @@
1
1
  # typed: strict
2
2
  require ('sorbet-rails/model_plugins/base')
3
+ require("sorbet-rails/utils")
3
4
  class SorbetRails::ModelPlugins::ActiveRecordNamedScope < SorbetRails::ModelPlugins::Base
4
5
 
5
6
  sig { implementation.params(root: Parlour::RbiGenerator::Namespace).void }
@@ -7,6 +8,7 @@ class SorbetRails::ModelPlugins::ActiveRecordNamedScope < SorbetRails::ModelPlug
7
8
  model_class_rbi = root.create_class(self.model_class_name)
8
9
 
9
10
  @model_class.methods.sort.each do |method_name|
11
+ next unless SorbetRails::Utils.valid_method_name?(method_name.to_s)
10
12
  method_obj = @model_class.method(method_name)
11
13
  next unless method_obj.present? && method_obj.source_location.present?
12
14
  # we detect sscopes defined in a model by 2 criteria:
@@ -0,0 +1,27 @@
1
+ # typed: false
2
+
3
+ require 'singleton'
4
+
5
+ class ActiveRecordOverrides
6
+ include Singleton
7
+
8
+ attr_reader :enum_calls
9
+
10
+ def initialize
11
+ @enum_calls = {}
12
+ end
13
+
14
+ def store_enum_call(class_name, kwargs)
15
+ @enum_calls[class_name] ||= []
16
+ @enum_calls[class_name] << kwargs
17
+ end
18
+ end
19
+
20
+ module ::ActiveRecord::Enum
21
+ alias old_enum enum
22
+
23
+ def enum(*args, **kwargs)
24
+ ActiveRecordOverrides.instance.store_enum_call(T.unsafe(self).name, kwargs)
25
+ old_enum(*args, **kwargs)
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ # typed: true
2
+ require ('sorbet-rails/model_plugins/base')
3
+ class SorbetRails::ModelPlugins::ActiveStorageMethods < SorbetRails::ModelPlugins::Base
4
+ sig { params(model_class: T.class_of(ActiveRecord::Base), available_classes: T::Set[String]).void }
5
+ def initialize(model_class, available_classes)
6
+ super
7
+ end
8
+
9
+ sig { implementation.params(root: Parlour::RbiGenerator::Namespace).void }
10
+ def generate(root)
11
+ # Check that ActiveStorage the attachment_reflections method exists
12
+ # It was added in 6.0, so it isn't available for 5.2.
13
+ return unless defined?(@model_class.attachment_reflections) && @model_class.attachment_reflections.length > 0
14
+
15
+ assoc_module_name = self.model_module_name("GeneratedAssociationMethods")
16
+ assoc_module_rbi = root.create_module(assoc_module_name)
17
+
18
+ attachment_reflections = @model_class.attachment_reflections.transform_values { |attachment| attachment.class }
19
+
20
+ attachment_reflections.each do |assoc_name, attachment_type|
21
+ if attachment_type.to_s == 'ActiveStorage::Reflection::HasOneAttachedReflection'
22
+ create_has_one_methods(assoc_name, assoc_module_rbi)
23
+ elsif attachment_type.to_s == 'ActiveStorage::Reflection::HasManyAttachedReflection'
24
+ create_has_many_methods(assoc_name, assoc_module_rbi)
25
+ end
26
+ end
27
+ end
28
+
29
+ sig { params(assoc_name: String, mod: Parlour::RbiGenerator::Namespace).void }
30
+ def create_has_one_methods(assoc_name, mod)
31
+ mod.create_method(
32
+ assoc_name,
33
+ return_type: 'T.nilable(ActiveStorage::Attached::One)'
34
+ )
35
+
36
+ mod.create_method(
37
+ "#{assoc_name}=",
38
+ parameters: [
39
+ Parameter.new('attachable', type: 'T.untyped')
40
+ ],
41
+ return_type: 'T.untyped'
42
+ )
43
+ end
44
+
45
+ sig { params(assoc_name: String, mod: Parlour::RbiGenerator::Namespace).void }
46
+ def create_has_many_methods(assoc_name, mod)
47
+ mod.create_method(
48
+ assoc_name,
49
+ return_type: 'T.nilable(ActiveStorage::Attached::Many)'
50
+ )
51
+
52
+ mod.create_method(
53
+ "#{assoc_name}=",
54
+ parameters: [
55
+ Parameter.new('*attachables', type: 'T.untyped')
56
+ ],
57
+ return_type: 'T.untyped'
58
+ )
59
+ end
60
+ end
@@ -9,6 +9,8 @@ require('sorbet-rails/model_plugins/active_record_assoc')
9
9
  require('sorbet-rails/model_plugins/active_record_finder_methods')
10
10
  require('sorbet-rails/model_plugins/custom_finder_methods')
11
11
  require('sorbet-rails/model_plugins/enumerable_collections')
12
+ require('sorbet-rails/model_plugins/active_record_overrides')
13
+ require('sorbet-rails/model_plugins/active_storage_methods')
12
14
 
13
15
  module SorbetRails::ModelPlugins
14
16
  extend T::Sig
@@ -57,6 +59,8 @@ module SorbetRails::ModelPlugins
57
59
  CustomFinderMethods
58
60
  when :enumerable_collections
59
61
  EnumerableCollections
62
+ when :active_storage_methods
63
+ ActiveStorageMethods
60
64
  when :kaminari
61
65
  require('sorbet-rails/gem_plugins/kaminari_plugin')
62
66
  KaminariPlugin
@@ -0,0 +1,68 @@
1
+ # typed: false
2
+
3
+ require('parlour')
4
+ require('sorbet-runtime')
5
+
6
+ module SorbetRails::SorbetUtils
7
+ extend T::Sig
8
+
9
+ sig { params(method_def: UnboundMethod).returns(T::Array[Parlour::RbiGenerator::Parameter]) }
10
+ def self.parameters_from_method_def(method_def)
11
+ signature = T::Private::Methods.signature_for_method(method_def)
12
+
13
+ parameters_with_type = signature.nil? ?
14
+ method_def.parameters.map { |p| p + ['T.untyped'] } : # append untyped to each
15
+ get_ordered_parameters_with_type(signature)
16
+
17
+ parameters_with_type.map do |param_def|
18
+ param_name = param_def[1]
19
+ prefix =
20
+ case param_def[0]
21
+ when :rest; '*'
22
+ when :keyrest; '**'
23
+ when :block; '&'
24
+ # being comprehensive
25
+ when :req, :opt; ''
26
+ when :key, :keyreq; ''
27
+ else ''
28
+ end
29
+
30
+ param_type = param_def[2].to_s
31
+ if param_def[0] == :block
32
+ # special case `.void` in a proc
33
+ # see https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/types/proc.rb#L10
34
+ param_type = param_type.gsub('returns(<VOID>)', 'void')
35
+ end
36
+
37
+ ::Parlour::RbiGenerator::Parameter.new("#{prefix}#{param_name}", type: param_type)
38
+ end
39
+ end
40
+
41
+ sig {
42
+ params(signature: T::Private::Methods::Signature).
43
+ returns(T::Array[[Symbol, Symbol, T::Types::Base]])
44
+ }
45
+ def self.get_ordered_parameters_with_type(signature)
46
+ # extract original method param from signature
47
+ # https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/private/methods/signature.rb#L5-L8
48
+ params = []
49
+ signature.arg_types.each do |arg_type|
50
+ # could be :opt, but doesn't matter
51
+ params << [:req, arg_type[0], arg_type[1]]
52
+ end
53
+ signature.req_kwarg_names.each do |kwarg_name|
54
+ # could be :key, but doesn't matter
55
+ params << [:keyreq, kwarg_name, signature.kwarg_types[kwarg_name]]
56
+ end
57
+ if signature.has_rest
58
+ params << [:rest, signature.rest_name, signature.rest_type]
59
+ end
60
+ if signature.has_keyrest
61
+ params << [:keyrest, signature.keyrest_name, signature.keyrest_type]
62
+ end
63
+ if !signature.block_name.nil?
64
+ params << [:block, signature.block_name, signature.block_type]
65
+ end
66
+ params
67
+ end
68
+ end