sorbet-rails 0.6.3 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -2
  3. data/.gitignore +2 -1
  4. data/.travis.yml +3 -1
  5. data/README.md +54 -7
  6. data/Rakefile +3 -3
  7. data/lib/bundled_rbi/customizabel_rbi_formatter.rbi +29 -0
  8. data/lib/bundled_rbi/pluck_to_tstruct.rbi +2 -1
  9. data/lib/bundled_rbi/typed_enum.rbi +7 -0
  10. data/lib/sorbet-rails.rb +1 -3
  11. data/lib/sorbet-rails/active_record_rbi_formatter.rb +8 -2
  12. data/lib/sorbet-rails/config.rb +11 -0
  13. data/lib/sorbet-rails/deprecation.rb +1 -0
  14. data/lib/sorbet-rails/gem_plugins/active_flag_plugin.rb +0 -1
  15. data/lib/sorbet-rails/gem_plugins/kaminari_plugin.rb +8 -0
  16. data/lib/sorbet-rails/gem_plugins/paperclip_plugin.rb +0 -1
  17. data/lib/sorbet-rails/job_rbi_formatter.rb +73 -62
  18. data/lib/sorbet-rails/mailer_rbi_formatter.rb +40 -27
  19. data/lib/sorbet-rails/model_column_utils.rb +129 -0
  20. data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +37 -3
  21. data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +0 -103
  22. data/lib/sorbet-rails/model_plugins/active_record_enum.rb +0 -1
  23. data/lib/sorbet-rails/model_plugins/active_record_named_scope.rb +15 -6
  24. data/lib/sorbet-rails/model_plugins/active_record_querying.rb +34 -3
  25. data/lib/sorbet-rails/model_plugins/active_storage_methods.rb +1 -1
  26. data/lib/sorbet-rails/model_plugins/base.rb +0 -9
  27. data/lib/sorbet-rails/model_rbi_formatter.rb +4 -8
  28. data/lib/sorbet-rails/model_utils.rb +73 -32
  29. data/lib/sorbet-rails/rails_mixins/generated_url_helpers.rb +2 -3
  30. data/lib/sorbet-rails/rails_mixins/pluck_to_tstruct.rb +17 -7
  31. data/lib/sorbet-rails/railtie.rb +0 -2
  32. data/lib/sorbet-rails/sorbet_utils.rb +152 -150
  33. data/lib/sorbet-rails/tasks/rails_rbi.rake +18 -26
  34. data/sorbet-rails.gemspec +1 -1
  35. data/spec/generators/rails-template.rb +3 -6
  36. data/spec/generators/sorbet_test_cases.rb +66 -88
  37. data/spec/job_rbi_formatter_spec.rb +1 -1
  38. data/spec/pluck_to_tstruct_spec.rb +74 -1
  39. data/spec/rails_helper.rb +2 -2
  40. data/spec/rake_rails_rbi_jobs_spec.rb +20 -0
  41. data/spec/rake_rails_rbi_mailers_spec.rb +21 -0
  42. data/spec/support/v5.0/Gemfile +1 -1
  43. data/spec/support/v5.0/Gemfile.lock +9 -9
  44. data/spec/support/v5.0/app/models/spell_book.rb +2 -0
  45. data/spec/support/v5.0/app/models/wizard.rb +1 -1
  46. data/spec/support/v5.0/sorbet_test_cases.rb +66 -88
  47. data/spec/support/v5.1/Gemfile.lock +7 -7
  48. data/spec/support/v5.1/app/models/spell_book.rb +2 -0
  49. data/spec/support/v5.1/app/models/wizard.rb +1 -1
  50. data/spec/support/v5.1/sorbet_test_cases.rb +66 -88
  51. data/spec/support/v5.2/Gemfile +1 -1
  52. data/spec/support/v5.2/Gemfile.lock +9 -9
  53. data/spec/support/v5.2/app/models/spell_book.rb +2 -0
  54. data/spec/support/v5.2/app/models/wizard.rb +1 -1
  55. data/spec/support/v5.2/sorbet_test_cases.rb +66 -88
  56. data/spec/support/v6.0/.gitignore +6 -0
  57. data/spec/support/v6.0/Gemfile +3 -3
  58. data/spec/support/v6.0/Gemfile.lock +77 -76
  59. data/spec/support/v6.0/app/models/spell_book.rb +2 -0
  60. data/spec/support/v6.0/app/models/wizard.rb +1 -1
  61. data/spec/support/v6.0/bin/bundle +22 -13
  62. data/spec/support/v6.0/config/environments/test.rb +1 -1
  63. data/spec/support/v6.0/sorbet_test_cases.rb +66 -88
  64. data/spec/support/v6.0/tmp/pids/.keep +0 -0
  65. data/spec/test_data/v5.0/expected_active_record_base.rbi +2 -2
  66. data/spec/test_data/v5.0/expected_active_record_relation.rbi +2 -2
  67. data/spec/test_data/v5.0/expected_custom_application_job.rbi +21 -0
  68. data/spec/test_data/v5.0/expected_custom_application_mailer.rbi +6 -0
  69. data/spec/test_data/v5.0/expected_custom_award_house_point_hourglasses.rbi +21 -0
  70. data/spec/test_data/v5.0/expected_custom_daily_prophet_mailer.rbi +8 -0
  71. data/spec/test_data/v5.0/expected_custom_hogwarts_acceptance_mailer.rbi +21 -0
  72. data/spec/test_data/v5.0/expected_headmaster.rbi +60 -190
  73. data/spec/test_data/v5.0/expected_internal_metadata.rbi +42 -188
  74. data/spec/test_data/v5.0/expected_potion.rbi +51 -188
  75. data/spec/test_data/v5.0/expected_robe.rbi +51 -190
  76. data/spec/test_data/v5.0/expected_schema_migration.rbi +42 -188
  77. data/spec/test_data/v5.0/expected_school.rbi +51 -190
  78. data/spec/test_data/v5.0/expected_spell.rbi +42 -190
  79. data/spec/test_data/v5.0/expected_spell/habtm_spell_books.rbi +60 -190
  80. data/spec/test_data/v5.0/expected_spell_book.rbi +86 -215
  81. data/spec/test_data/v5.0/expected_spell_book/habtm_spells.rbi +60 -190
  82. data/spec/test_data/v5.0/expected_squib.rbi +131 -263
  83. data/spec/test_data/v5.0/expected_subject.rbi +42 -190
  84. data/spec/test_data/v5.0/expected_subject/habtm_wizards.rbi +60 -190
  85. data/spec/test_data/v5.0/expected_wand.rbi +84 -225
  86. data/spec/test_data/v5.0/expected_wizard.rbi +131 -263
  87. data/spec/test_data/v5.0/expected_wizard/habtm_subjects.rbi +60 -190
  88. data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +131 -263
  89. data/spec/test_data/v5.1/expected_active_record_base.rbi +2 -2
  90. data/spec/test_data/v5.1/expected_active_record_relation.rbi +2 -2
  91. data/spec/test_data/v5.1/expected_custom_application_job.rbi +21 -0
  92. data/spec/test_data/v5.1/expected_custom_application_mailer.rbi +6 -0
  93. data/spec/test_data/v5.1/expected_custom_award_house_point_hourglasses.rbi +21 -0
  94. data/spec/test_data/v5.1/expected_custom_daily_prophet_mailer.rbi +8 -0
  95. data/spec/test_data/v5.1/expected_custom_hogwarts_acceptance_mailer.rbi +21 -0
  96. data/spec/test_data/v5.1/expected_headmaster.rbi +60 -196
  97. data/spec/test_data/v5.1/expected_internal_metadata.rbi +42 -194
  98. data/spec/test_data/v5.1/expected_potion.rbi +51 -194
  99. data/spec/test_data/v5.1/expected_robe.rbi +51 -196
  100. data/spec/test_data/v5.1/expected_schema_migration.rbi +42 -194
  101. data/spec/test_data/v5.1/expected_school.rbi +51 -196
  102. data/spec/test_data/v5.1/expected_spell.rbi +42 -196
  103. data/spec/test_data/v5.1/expected_spell/habtm_spell_books.rbi +60 -196
  104. data/spec/test_data/v5.1/expected_spell_book.rbi +86 -221
  105. data/spec/test_data/v5.1/expected_spell_book/habtm_spells.rbi +60 -196
  106. data/spec/test_data/v5.1/expected_squib.rbi +132 -270
  107. data/spec/test_data/v5.1/expected_subject.rbi +42 -196
  108. data/spec/test_data/v5.1/expected_subject/habtm_wizards.rbi +60 -196
  109. data/spec/test_data/v5.1/expected_wand.rbi +84 -231
  110. data/spec/test_data/v5.1/expected_wizard.rbi +132 -270
  111. data/spec/test_data/v5.1/expected_wizard/habtm_subjects.rbi +60 -196
  112. data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +132 -270
  113. data/spec/test_data/v5.2/expected_active_record_base.rbi +2 -2
  114. data/spec/test_data/v5.2/expected_active_record_relation.rbi +2 -2
  115. data/spec/test_data/v5.2/expected_attachment.rbi +60 -194
  116. data/spec/test_data/v5.2/expected_blob.rbi +80 -214
  117. data/spec/test_data/v5.2/expected_custom_application_job.rbi +21 -0
  118. data/spec/test_data/v5.2/expected_custom_application_mailer.rbi +6 -0
  119. data/spec/test_data/v5.2/expected_custom_award_house_point_hourglasses.rbi +21 -0
  120. data/spec/test_data/v5.2/expected_custom_daily_prophet_mailer.rbi +8 -0
  121. data/spec/test_data/v5.2/expected_custom_hogwarts_acceptance_mailer.rbi +21 -0
  122. data/spec/test_data/v5.2/expected_headmaster.rbi +60 -196
  123. data/spec/test_data/v5.2/expected_internal_metadata.rbi +42 -194
  124. data/spec/test_data/v5.2/expected_potion.rbi +51 -194
  125. data/spec/test_data/v5.2/expected_robe.rbi +51 -196
  126. data/spec/test_data/v5.2/expected_schema_migration.rbi +42 -194
  127. data/spec/test_data/v5.2/expected_school.rbi +51 -196
  128. data/spec/test_data/v5.2/expected_spell.rbi +42 -196
  129. data/spec/test_data/v5.2/expected_spell/habtm_spell_books.rbi +60 -196
  130. data/spec/test_data/v5.2/expected_spell_book.rbi +86 -221
  131. data/spec/test_data/v5.2/expected_spell_book/habtm_spells.rbi +60 -196
  132. data/spec/test_data/v5.2/expected_squib.rbi +156 -276
  133. data/spec/test_data/v5.2/expected_subject.rbi +42 -196
  134. data/spec/test_data/v5.2/expected_subject/habtm_wizards.rbi +60 -196
  135. data/spec/test_data/v5.2/expected_wand.rbi +84 -231
  136. data/spec/test_data/v5.2/expected_wizard.rbi +156 -276
  137. data/spec/test_data/v5.2/expected_wizard/habtm_subjects.rbi +60 -196
  138. data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +156 -276
  139. data/spec/test_data/v6.0/expected_active_record_base.rbi +2 -2
  140. data/spec/test_data/v6.0/expected_active_record_relation.rbi +10 -10
  141. data/spec/test_data/v6.0/expected_attachment.rbi +60 -218
  142. data/spec/test_data/v6.0/expected_blob.rbi +80 -238
  143. data/spec/test_data/v6.0/expected_custom_application_job.rbi +21 -0
  144. data/spec/test_data/v6.0/expected_custom_application_mailer.rbi +6 -0
  145. data/spec/test_data/v6.0/expected_custom_award_house_point_hourglasses.rbi +21 -0
  146. data/spec/test_data/v6.0/expected_custom_daily_prophet_mailer.rbi +8 -0
  147. data/spec/test_data/v6.0/expected_custom_hogwarts_acceptance_mailer.rbi +21 -0
  148. data/spec/test_data/v6.0/expected_headmaster.rbi +60 -220
  149. data/spec/test_data/v6.0/expected_internal_metadata.rbi +42 -218
  150. data/spec/test_data/v6.0/expected_potion.rbi +51 -218
  151. data/spec/test_data/v6.0/expected_robe.rbi +51 -220
  152. data/spec/test_data/v6.0/expected_routes.rbi +14 -7
  153. data/spec/test_data/v6.0/expected_schema_migration.rbi +42 -218
  154. data/spec/test_data/v6.0/expected_school.rbi +51 -220
  155. data/spec/test_data/v6.0/expected_spell.rbi +42 -220
  156. data/spec/test_data/v6.0/expected_spell/habtm_spell_books.rbi +60 -220
  157. data/spec/test_data/v6.0/expected_spell_book.rbi +98 -257
  158. data/spec/test_data/v6.0/expected_spell_book/habtm_spells.rbi +60 -220
  159. data/spec/test_data/v6.0/expected_squib.rbi +182 -326
  160. data/spec/test_data/v6.0/expected_subject.rbi +42 -220
  161. data/spec/test_data/v6.0/expected_subject/habtm_wizards.rbi +60 -220
  162. data/spec/test_data/v6.0/expected_wand.rbi +100 -271
  163. data/spec/test_data/v6.0/expected_wizard.rbi +182 -326
  164. data/spec/test_data/v6.0/expected_wizard/habtm_subjects.rbi +60 -220
  165. data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +182 -326
  166. metadata +46 -11
  167. data/lib/bundled_rbi/parameters.rbi +0 -28
  168. data/lib/sorbet-rails/custom_types/boolean_string.rb +0 -42
  169. data/lib/sorbet-rails/custom_types/integer_string.rb +0 -45
  170. data/lib/sorbet-rails/rails_mixins/custom_params_methods.rb +0 -57
  171. data/spec/boolean_string_spec.rb +0 -59
  172. data/spec/custom_params_methods_spec.rb +0 -138
  173. data/spec/integer_string_spec.rb +0 -46
@@ -52,7 +52,7 @@ class SorbetRails::ModelPlugins::ActiveStorageMethods < SorbetRails::ModelPlugin
52
52
  mod.create_method(
53
53
  "#{assoc_name}=",
54
54
  parameters: [
55
- Parameter.new('*attachables', type: 'T.untyped')
55
+ Parameter.new('attachables', type: 'T.untyped')
56
56
  ],
57
57
  return_type: 'T.untyped'
58
58
  )
@@ -29,14 +29,5 @@ module SorbetRails::ModelPlugins
29
29
  @model_class = T.let(model_class, T.class_of(ActiveRecord::Base))
30
30
  @available_classes = T.let(available_classes, T::Set[String])
31
31
  end
32
-
33
- sig { params(attribute: T.any(String, Symbol)).returns(T::Boolean) }
34
- def attribute_has_unconditional_presence_validation?(attribute)
35
- model_class.validators_on(attribute).any? do |validator|
36
- validator.is_a?(ActiveModel::Validations::PresenceValidator) &&
37
- !validator.options.key?(:if) &&
38
- !validator.options.key?(:unless)
39
- end
40
- end
41
32
  end
42
33
  end
@@ -74,8 +74,7 @@ class SorbetRails::ModelRbiFormatter
74
74
  self.model_relation_class_name,
75
75
  superclass: "ActiveRecord::Relation",
76
76
  )
77
- model_relation_rbi.create_extend("T::Sig")
78
- model_relation_rbi.create_extend("T::Generic")
77
+ model_relation_rbi.create_include(self.model_query_methods_returning_relation_module_name)
79
78
  model_relation_rbi.create_constant(
80
79
  "Elem",
81
80
  value: "type_member(fixed: #{model_class_name})",
@@ -85,8 +84,7 @@ class SorbetRails::ModelRbiFormatter
85
84
  self.model_assoc_relation_class_name,
86
85
  superclass: "ActiveRecord::AssociationRelation",
87
86
  )
88
- model_assoc_relation_rbi.create_extend("T::Sig")
89
- model_assoc_relation_rbi.create_extend("T::Generic")
87
+ model_assoc_relation_rbi.create_include(self.model_query_methods_returning_assoc_relation_module_name)
90
88
  model_assoc_relation_rbi.create_constant(
91
89
  "Elem",
92
90
  value: "type_member(fixed: #{model_class_name})",
@@ -96,8 +94,7 @@ class SorbetRails::ModelRbiFormatter
96
94
  self.model_assoc_proxy_class_name,
97
95
  superclass: "ActiveRecord::Associations::CollectionProxy",
98
96
  )
99
- collection_proxy_rbi.create_extend("T::Sig")
100
- collection_proxy_rbi.create_extend("T::Generic")
97
+ collection_proxy_rbi.create_include(self.model_query_methods_returning_assoc_relation_module_name)
101
98
  collection_proxy_rbi.create_constant(
102
99
  "Elem",
103
100
  value: "type_member(fixed: #{self.model_class_name})",
@@ -107,8 +104,7 @@ class SorbetRails::ModelRbiFormatter
107
104
  self.model_class_name,
108
105
  superclass: T.must(@model_class.superclass).name,
109
106
  )
110
- model_rbi.create_extend("T::Sig")
111
- model_rbi.create_extend("T::Generic")
107
+ model_rbi.create_extend(self.model_query_methods_returning_relation_module_name)
112
108
  model_rbi.create_type_alias(
113
109
  self.model_relation_type_class_name,
114
110
  type: self.model_relation_type_alias
@@ -1,14 +1,17 @@
1
1
  # typed: strict
2
+ require('sorbet-rails/model_column_utils')
2
3
  module SorbetRails::ModelUtils
3
4
  extend T::Sig
4
5
  extend T::Helpers
6
+ include SorbetRails::ModelColumnUtils
5
7
 
6
8
  abstract!
7
9
 
8
10
  # if we're a HABTM class then model_class is an anonymous class (see the rails link below) and
9
11
  # i'm not sure how to explain that to sorbet other than T.class_of(Class).
10
- sig { abstract.returns(T.any(T.class_of(ActiveRecord::Base), T.class_of(Class))) }
11
- def model_class; end
12
+ # This is also defined in ModelColumnUtils
13
+ # sig { abstract.returns(T.any(T.class_of(ActiveRecord::Base), T.class_of(Class))) }
14
+ # def model_class; end
12
15
 
13
16
  sig { returns(T::Boolean) }
14
17
  def habtm_class?
@@ -37,6 +40,16 @@ module SorbetRails::ModelUtils
37
40
  "#{model_class_name}::ActiveRecord_AssociationRelation"
38
41
  end
39
42
 
43
+ sig { returns(String) }
44
+ def model_query_methods_returning_relation_module_name
45
+ "#{model_class_name}::QueryMethodsReturningRelation"
46
+ end
47
+
48
+ sig { returns(String) }
49
+ def model_query_methods_returning_assoc_relation_module_name
50
+ "#{model_class_name}::QueryMethodsReturningAssociationRelation"
51
+ end
52
+
40
53
  sig { returns(String) }
41
54
  def model_relation_type_alias
42
55
  types = [
@@ -73,9 +86,13 @@ module SorbetRails::ModelUtils
73
86
  root: Parlour::RbiGenerator::Namespace,
74
87
  method_name: String,
75
88
  parameters: T.nilable(T::Array[::Parlour::RbiGenerator::Parameter]),
89
+ # This is meant to indicate the method is a rails-provided query method like
90
+ # where, limit, etc and not something like a named scope. It should likely
91
+ # only be set to `true` when called from the ActiveRecordQuerying plugin.
92
+ builtin_query_method: T::Boolean,
76
93
  ).void
77
94
  }
78
- def add_relation_query_method(root, method_name, parameters: nil)
95
+ def add_relation_query_method(root, method_name, parameters: nil, builtin_query_method: false)
79
96
  # a relation querying method will be available on
80
97
  # - model (as a class method)
81
98
  # - activerecord relation
@@ -84,34 +101,58 @@ module SorbetRails::ModelUtils
84
101
  # in case (1) and (2), it returns a Model::ActiveRecord_Relation
85
102
  # in case (3) and (4), it returns a Model::ActiveRecord_AssociationRelation
86
103
 
87
- # force generating these methods because sorbet's hidden-definitions generate & override them
88
- model_class_rbi = root.create_class(self.model_class_name)
89
- model_class_rbi.create_method(
90
- method_name,
91
- parameters: parameters,
92
- return_type: self.model_relation_class_name,
93
- class_method: true,
94
- )
95
-
96
- model_relation_rbi = root.create_class(self.model_relation_class_name)
97
- model_relation_rbi.create_method(
98
- method_name,
99
- parameters: parameters,
100
- return_type: self.model_relation_class_name,
101
- )
102
-
103
- model_assoc_relation_rbi = root.create_class(self.model_assoc_relation_class_name)
104
- model_assoc_relation_rbi.create_method(
105
- method_name,
106
- parameters: parameters,
107
- return_type: self.model_assoc_relation_class_name,
108
- )
109
-
110
- collection_proxy_rbi = root.create_class(self.model_assoc_proxy_class_name)
111
- collection_proxy_rbi.create_method(
112
- method_name,
113
- parameters: parameters,
114
- return_type: self.model_assoc_relation_class_name,
115
- )
104
+ # 'unscoped' is a special case where it always returns a ActiveRecord_Relation
105
+ assoc_return_value = method_name == 'unscoped' ? self.model_relation_class_name : self.model_assoc_relation_class_name
106
+
107
+ # We can put methods onto modules which are extended/included by the model
108
+ # and relation classes which reduces the RBI footprint for an individual
109
+ # model. However, in Rails 5 query methods that come from scopes or enums
110
+ # get overridden in hidden-definitions so we need to explicitly define them
111
+ # on the model and relation classes.
112
+ if builtin_query_method
113
+ relation_module_rbi = root.create_module(self.model_query_methods_returning_relation_module_name)
114
+ relation_module_rbi.create_method(
115
+ method_name,
116
+ parameters: parameters,
117
+ return_type: self.model_relation_class_name,
118
+ )
119
+
120
+ assoc_relation_module_rbi = root.create_module(self.model_query_methods_returning_assoc_relation_module_name)
121
+ assoc_relation_module_rbi.create_method(
122
+ method_name,
123
+ parameters: parameters,
124
+ return_type: assoc_return_value,
125
+ )
126
+ else
127
+ # force generating these methods because sorbet's hidden-definitions generate & override them
128
+ model_class_rbi = root.create_class(self.model_class_name)
129
+ model_class_rbi.create_method(
130
+ method_name,
131
+ parameters: parameters,
132
+ return_type: self.model_relation_class_name,
133
+ class_method: true,
134
+ )
135
+
136
+ model_relation_rbi = root.create_class(self.model_relation_class_name)
137
+ model_relation_rbi.create_method(
138
+ method_name,
139
+ parameters: parameters,
140
+ return_type: self.model_relation_class_name,
141
+ )
142
+
143
+ model_assoc_relation_rbi = root.create_class(self.model_assoc_relation_class_name)
144
+ model_assoc_relation_rbi.create_method(
145
+ method_name,
146
+ parameters: parameters,
147
+ return_type: assoc_return_value,
148
+ )
149
+
150
+ collection_proxy_rbi = root.create_class(self.model_assoc_proxy_class_name)
151
+ collection_proxy_rbi.create_method(
152
+ method_name,
153
+ parameters: parameters,
154
+ return_type: assoc_return_value,
155
+ )
156
+ end
116
157
  end
117
158
  end
@@ -11,6 +11,5 @@
11
11
  # + include GeneratedUrlHelpers
12
12
  # end
13
13
  #
14
- module GeneratedUrlHelpers
15
- include Rails.application.routes.url_helpers
16
- end
14
+ # Reference: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/route_set.rb
15
+ GeneratedUrlHelpers = Rails.application.routes.url_helpers
@@ -7,27 +7,37 @@ module SorbetRails::PluckToTStruct
7
7
  type_parameters(:U).
8
8
  params(
9
9
  ta_struct: ITypeAssert[T.type_parameter(:U)],
10
+ associations: T::Hash[Symbol, String],
10
11
  ).
11
12
  returns(T::Array[T.type_parameter(:U)])
12
13
  }
13
- def pluck_to_tstruct(ta_struct)
14
+ def pluck_to_tstruct(ta_struct, associations: {})
14
15
  tstruct = ta_struct.get_type
15
-
16
+
16
17
  if !(tstruct < T::Struct)
17
18
  raise UnexpectedType.new("pluck_to_tstruct expects a tstruct subclass, given #{tstruct}")
18
19
  end
19
20
 
20
- keys = tstruct.props.keys
21
+ tstruct_keys = tstruct.props.keys
22
+ associations_keys = associations.keys
23
+ invalid_keys = associations_keys - tstruct_keys
24
+
25
+ if invalid_keys.any?
26
+ raise UnexpectedAssociations.new("Argument 'associations' contains keys that don't exist in #{tstruct}: #{invalid_keys.join(", ")}")
27
+ end
28
+
29
+ pluck_keys = (tstruct_keys - associations_keys) + associations.values
21
30
 
22
31
  # loosely based on pluck_to_hash gem
23
32
  # https://github.com/girishso/pluck_to_hash/blob/master/lib/pluck_to_hash.rb
24
- keys_one = keys.size == 1
25
- pluck(*keys).map do |row|
33
+ keys_one = pluck_keys.size == 1
34
+ pluck(*pluck_keys).map do |row|
26
35
  row = [row] if keys_one
27
- value = Hash[keys.zip(row)]
36
+ value = Hash[tstruct_keys.zip(row)]
28
37
  tstruct.new(value)
29
38
  end
30
39
  end
31
-
40
+
32
41
  class UnexpectedType < StandardError; end
42
+ class UnexpectedAssociations < StandardError; end
33
43
  end
@@ -50,9 +50,7 @@ class SorbetRails::Railtie < Rails::Railtie
50
50
  end
51
51
 
52
52
  ActiveSupport.on_load(:action_controller) do
53
- require "sorbet-rails/rails_mixins/custom_params_methods"
54
53
  require "sorbet-rails/rails_mixins/generated_url_helpers"
55
- ActionController::Parameters.include SorbetRails::CustomParamsMethods
56
54
  end
57
55
 
58
56
  SorbetRails.register_configured_plugins
@@ -5,168 +5,170 @@ require('sorbet-runtime')
5
5
  require('method_source')
6
6
  require('parser/current')
7
7
 
8
- module SorbetRails::SorbetUtils
9
- extend T::Sig
10
- include Kernel
11
-
12
- class ParsedParamDef < T::Struct
13
- const :name, Symbol
14
- const :kind, Symbol
15
- const :type_str, String
16
- prop :default, T.nilable(String), default: nil
17
- prop :prefix, T.nilable(String)
18
- prop :suffix, T.nilable(String)
19
- end
20
-
21
- sig { params(method_def: UnboundMethod).returns(T::Array[Parlour::RbiGenerator::Parameter]) }
22
- def self.parameters_from_method_def(method_def)
23
- signature = T::Private::Methods.signature_for_method(method_def)
24
- method_def = signature.nil? ? method_def : signature.method
25
-
26
- parameters_with_type = signature.nil? ?
27
- method_def.parameters.map { |p|
28
- ParsedParamDef.new(
29
- name: p.size == 1 ? :_ : p[1], # give param without name default name _
30
- kind: p[0], # append untyped as type of each param
31
- type_str: 'T.untyped',
32
- )
33
- } :
34
- get_ordered_parameters_with_type(signature)
35
-
36
- # add prefix & suffix
37
- parameters_with_type.each do |param_def|
38
- param_def.prefix =
39
- case param_def.kind
40
- when :rest; '*'
41
- when :keyrest; '**'
42
- when :block; '&'
43
- # being comprehensive
44
- when :req, :opt; ''
45
- when :key, :keyreq; ''
46
- else nil
47
- end
48
-
49
- param_def.suffix =
50
- case param_def.kind
51
- when :key, :keyreq; ':'
52
- else nil
53
- end
8
+ module SorbetRails
9
+ module SorbetUtils
10
+ extend T::Sig
11
+ include Kernel
12
+
13
+ class ParsedParamDef < T::Struct
14
+ const :name, Symbol
15
+ const :kind, Symbol
16
+ const :type_str, String
17
+ prop :default, T.nilable(String), default: nil
18
+ prop :prefix, T.nilable(String)
19
+ prop :suffix, T.nilable(String)
54
20
  end
55
21
 
56
- extract_default_value_for_params!(
57
- parameters_with_type,
58
- method_def,
59
- )
60
-
61
- parameters_with_type.map do |param_def|
62
- ::Parlour::RbiGenerator::Parameter.new(
63
- "#{param_def.prefix}#{param_def.name}#{param_def.suffix}",
64
- type: param_def.type_str,
65
- default: param_def.default,
22
+ sig { params(method_def: UnboundMethod).returns(T::Array[Parlour::RbiGenerator::Parameter]) }
23
+ def self.parameters_from_method_def(method_def)
24
+ signature = T::Private::Methods.signature_for_method(method_def)
25
+ method_def = signature.nil? ? method_def : signature.method
26
+
27
+ parameters_with_type = signature.nil? ?
28
+ method_def.parameters.map { |p|
29
+ ParsedParamDef.new(
30
+ name: p.size == 1 ? :_ : p[1], # give param without name default name _
31
+ kind: p[0], # append untyped as type of each param
32
+ type_str: 'T.untyped',
33
+ )
34
+ } :
35
+ get_ordered_parameters_with_type(signature)
36
+
37
+ # add prefix & suffix
38
+ parameters_with_type.each do |param_def|
39
+ param_def.prefix =
40
+ case param_def.kind
41
+ when :rest; '*'
42
+ when :keyrest; '**'
43
+ when :block; '&'
44
+ # being comprehensive
45
+ when :req, :opt; ''
46
+ when :key, :keyreq; ''
47
+ else nil
48
+ end
49
+
50
+ param_def.suffix =
51
+ case param_def.kind
52
+ when :key, :keyreq; ':'
53
+ else nil
54
+ end
55
+ end
56
+
57
+ extract_default_value_for_params!(
58
+ parameters_with_type,
59
+ method_def,
66
60
  )
67
- end
68
- end
69
61
 
70
- sig {
71
- params(signature: T::Private::Methods::Signature).
72
- returns(T::Array[ParsedParamDef])
73
- }
74
- def self.get_ordered_parameters_with_type(signature)
75
- # extract original method param from signature
76
- # https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/private/methods/signature.rb#L5-L8
77
- params = T.let([], T::Array[ParsedParamDef])
78
- signature.arg_types.each do |arg_type|
79
- # could be :opt, but doesn't matter
80
- params << ParsedParamDef.new(
81
- name: arg_type[0],
82
- kind: :req,
83
- type_str: arg_type[1].to_s,
84
- )
85
- end
86
- signature.kwarg_types.each do |kwarg_name, kwarg_type|
87
- # could be :key, but doesn't matter
88
- params << ParsedParamDef.new(
89
- name: kwarg_name,
90
- kind: :keyreq,
91
- type_str: kwarg_type.to_s,
92
- )
93
- end
94
- if signature.has_rest
95
- params << ParsedParamDef.new(
96
- name: signature.rest_name,
97
- kind: :rest,
98
- type_str: signature.rest_type.to_s,
99
- )
100
- end
101
- if signature.has_keyrest
102
- params << ParsedParamDef.new(
103
- name: signature.keyrest_name,
104
- kind: :keyrest,
105
- type_str: signature.keyrest_type.to_s,
106
- )
107
- end
108
- if !signature.block_name.nil?
109
- # special case `.void` in a proc
110
- # see https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/types/proc.rb#L10
111
- block_param_type = signature.block_type.to_s
112
- block_param_type = block_param_type.gsub('returns(<VOID>)', 'void')
113
- params << ParsedParamDef.new(
114
- name: signature.block_name,
115
- kind: :block,
116
- type_str: block_param_type,
117
- )
62
+ parameters_with_type.map do |param_def|
63
+ ::Parlour::RbiGenerator::Parameter.new(
64
+ "#{param_def.prefix}#{param_def.name}#{param_def.suffix}",
65
+ type: param_def.type_str,
66
+ default: param_def.default,
67
+ )
68
+ end
118
69
  end
119
- params
120
- end
121
70
 
122
- sig {
123
- params(
124
- parsed_params: T::Array[ParsedParamDef],
125
- method_def: UnboundMethod,
126
- ).void
127
- }
128
- def self.extract_default_value_for_params!(parsed_params, method_def)
129
- source = method_def.source
130
- parsed_ast = Parser::CurrentRuby.parse(source)
131
- if parsed_ast.type != :def
132
- # could be a method added at runtime? ignore it
133
- puts "Warning: unable to parse the source of #{method_def.name}"
134
- return
71
+ sig {
72
+ params(signature: T::Private::Methods::Signature).
73
+ returns(T::Array[ParsedParamDef])
74
+ }
75
+ def self.get_ordered_parameters_with_type(signature)
76
+ # extract original method param from signature
77
+ # https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/private/methods/signature.rb#L5-L8
78
+ params = T.let([], T::Array[ParsedParamDef])
79
+ signature.arg_types.each do |arg_type|
80
+ # could be :opt, but doesn't matter
81
+ params << ParsedParamDef.new(
82
+ name: arg_type[0],
83
+ kind: :req,
84
+ type_str: arg_type[1].to_s,
85
+ )
86
+ end
87
+ signature.kwarg_types.each do |kwarg_name, kwarg_type|
88
+ # could be :key, but doesn't matter
89
+ params << ParsedParamDef.new(
90
+ name: kwarg_name,
91
+ kind: :keyreq,
92
+ type_str: kwarg_type.to_s,
93
+ )
94
+ end
95
+ if signature.has_rest
96
+ params << ParsedParamDef.new(
97
+ name: signature.rest_name,
98
+ kind: :rest,
99
+ type_str: signature.rest_type.to_s,
100
+ )
101
+ end
102
+ if signature.has_keyrest
103
+ params << ParsedParamDef.new(
104
+ name: signature.keyrest_name,
105
+ kind: :keyrest,
106
+ type_str: signature.keyrest_type.to_s,
107
+ )
108
+ end
109
+ if !signature.block_name.nil?
110
+ # special case `.void` in a proc
111
+ # see https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/types/proc.rb#L10
112
+ block_param_type = signature.block_type.to_s
113
+ block_param_type = block_param_type.gsub('returns(<VOID>)', 'void')
114
+ params << ParsedParamDef.new(
115
+ name: signature.block_name,
116
+ kind: :block,
117
+ type_str: block_param_type,
118
+ )
119
+ end
120
+ params
135
121
  end
136
122
 
137
- args = parsed_ast.children[1]
138
- if args.type != :args
139
- puts "Warning: unable to parse the source of #{method_def.name}"
140
- return
123
+ sig {
124
+ params(
125
+ parsed_params: T::Array[ParsedParamDef],
126
+ method_def: UnboundMethod,
127
+ ).void
128
+ }
129
+ def self.extract_default_value_for_params!(parsed_params, method_def)
130
+ source = method_def.source
131
+ parsed_ast = Parser::CurrentRuby.parse(source)
132
+ if parsed_ast.type != :def
133
+ # could be a method added at runtime? ignore it
134
+ puts "Warning: unable to parse the source of #{method_def.name}"
135
+ return
136
+ end
137
+
138
+ args = parsed_ast.children[1]
139
+ if args.type != :args
140
+ puts "Warning: unable to parse the source of #{method_def.name}"
141
+ return
142
+ end
143
+
144
+ parsed_params_map = Hash[parsed_params.map {|p| [p.name, p]}]
145
+ args.children.each do |arg|
146
+ arg_name = arg.children[0]
147
+ default = arg.children[1] ? node_to_s(arg.children[1]) : nil
148
+
149
+ next if arg_name.blank?
150
+
151
+ param_def = parsed_params_map[arg_name]
152
+
153
+ raise UnexpectedParam.new(
154
+ "Unexpected param #{arg_name} when parsing #{method_def.name}"
155
+ ) unless param_def.present?
156
+
157
+ param_def.default = default
158
+ end
141
159
  end
142
160
 
143
- parsed_params_map = Hash[parsed_params.map {|p| [p.name, p]}]
144
- args.children.each do |arg|
145
- arg_name = arg.children[0]
146
- default = arg.children[1] ? node_to_s(arg.children[1]) : nil
147
-
148
- next if arg_name.blank?
149
-
150
- param_def = parsed_params_map[arg_name]
151
-
152
- raise UnexpectedParam.new(
153
- "Unexpected param #{arg_name} when parsing #{method_def.name}"
154
- ) unless param_def.present?
161
+ # Given an AST node, returns the source code from which it was constructed.
162
+ # If the given AST node is nil, this returns nil.
163
+ # Taken from https://github.com/AaronC81/parlour/blob/master/lib/parlour/type_parser.rb#L506
164
+ sig { params(node: T.nilable(Parser::AST::Node)).returns(T.nilable(String)) }
165
+ def self.node_to_s(node)
166
+ return nil unless node
155
167
 
156
- param_def.default = default
168
+ exp = node.loc.expression
169
+ exp.source_buffer.source[exp.begin_pos...exp.end_pos]
157
170
  end
158
- end
159
-
160
- # Given an AST node, returns the source code from which it was constructed.
161
- # If the given AST node is nil, this returns nil.
162
- # Taken from https://github.com/AaronC81/parlour/blob/master/lib/parlour/type_parser.rb#L506
163
- sig { params(node: T.nilable(Parser::AST::Node)).returns(T.nilable(String)) }
164
- def self.node_to_s(node)
165
- return nil unless node
166
171
 
167
- exp = node.loc.expression
168
- exp.source_buffer.source[exp.begin_pos...exp.end_pos]
172
+ class UnexpectedParam < StandardError; end
169
173
  end
170
-
171
- class UnexpectedParam < StandardError; end
172
174
  end