sorbet-rails 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sorbet-rails/active_record_rbi_formatter.rb +295 -0
  3. data/lib/sorbet-rails/config.rb +0 -1
  4. data/lib/sorbet-rails/custom_types/boolean_string.rb +10 -0
  5. data/lib/sorbet-rails/custom_types/integer_string.rb +10 -0
  6. data/lib/sorbet-rails/dependent_gem_rbis/activerecord.rbi +11 -0
  7. data/lib/sorbet-rails/deprecation.rb +5 -0
  8. data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +27 -6
  9. data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +20 -8
  10. data/lib/sorbet-rails/model_plugins/base.rb +9 -0
  11. data/lib/sorbet-rails/model_plugins/enumerable_collections.rb +0 -50
  12. data/lib/sorbet-rails/model_plugins/plugins.rb +0 -3
  13. data/lib/sorbet-rails/model_rbi_formatter.rb +2 -2
  14. data/lib/sorbet-rails/model_utils.rb +15 -6
  15. data/lib/sorbet-rails/rails_mixins/custom_params_methods.rb +11 -0
  16. data/lib/sorbet-rails/tasks/rails_rbi.rake +20 -4
  17. data/sorbet-rails.gemspec +2 -2
  18. data/spec/active_record_rbi_formatter_spec.rb +24 -0
  19. data/spec/generators/rails-template.rb +88 -1
  20. data/spec/generators/sorbet_test_cases.rb +146 -42
  21. data/spec/model_rbi_formatter_spec.rb +1 -1
  22. data/spec/rails_helper.rb +14 -1
  23. data/spec/rake_rails_rbi_active_record_spec.rb +21 -0
  24. data/spec/rake_rails_rbi_models_spec.rb +7 -0
  25. data/spec/sorbet_spec.rb +12 -1
  26. data/spec/support/v5.0/Gemfile.lock +8 -8
  27. data/spec/support/v5.0/app/models/headmaster.rb +8 -0
  28. data/spec/support/v5.0/app/models/school.rb +2 -0
  29. data/spec/support/v5.0/app/models/spell.rb +5 -0
  30. data/spec/support/v5.0/app/models/spell_book.rb +3 -0
  31. data/spec/support/v5.0/app/models/subject.rb +5 -0
  32. data/spec/support/v5.0/app/models/wizard.rb +6 -1
  33. data/spec/support/v5.0/db/migrate/20190620000010_add_subject.rb +8 -0
  34. data/spec/support/v5.0/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
  35. data/spec/support/v5.0/db/migrate/20190620000012_add_spell.rb +8 -0
  36. data/spec/support/v5.0/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
  37. data/spec/support/v5.0/db/migrate/20190620000014_create_headmasters.rb +9 -0
  38. data/spec/support/v5.0/db/schema.rb +28 -1
  39. data/spec/support/v5.0/lib/mythical_rbi_plugin.rb +1 -1
  40. data/spec/support/v5.0/sorbet_test_cases.rb +146 -42
  41. data/spec/support/v5.1/Gemfile.lock +8 -8
  42. data/spec/support/v5.1/app/models/headmaster.rb +8 -0
  43. data/spec/support/v5.1/app/models/school.rb +2 -0
  44. data/spec/support/v5.1/app/models/spell.rb +5 -0
  45. data/spec/support/v5.1/app/models/spell_book.rb +3 -0
  46. data/spec/support/v5.1/app/models/subject.rb +5 -0
  47. data/spec/support/v5.1/app/models/wizard.rb +6 -1
  48. data/spec/support/v5.1/db/migrate/20190620000010_add_subject.rb +8 -0
  49. data/spec/support/v5.1/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
  50. data/spec/support/v5.1/db/migrate/20190620000012_add_spell.rb +8 -0
  51. data/spec/support/v5.1/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
  52. data/spec/support/v5.1/db/migrate/20190620000014_create_headmasters.rb +9 -0
  53. data/spec/support/v5.1/db/schema.rb +28 -1
  54. data/spec/support/v5.1/lib/mythical_rbi_plugin.rb +1 -1
  55. data/spec/support/v5.1/sorbet_test_cases.rb +146 -42
  56. data/spec/support/v5.2/Gemfile +1 -1
  57. data/spec/support/v5.2/Gemfile.lock +9 -9
  58. data/spec/support/v5.2/app/models/headmaster.rb +8 -0
  59. data/spec/support/v5.2/app/models/school.rb +2 -0
  60. data/spec/support/v5.2/app/models/spell.rb +5 -0
  61. data/spec/support/v5.2/app/models/spell_book.rb +3 -0
  62. data/spec/support/v5.2/app/models/subject.rb +5 -0
  63. data/spec/support/v5.2/app/models/wizard.rb +5 -0
  64. data/spec/support/v5.2/config/puma.rb +3 -0
  65. data/spec/support/v5.2/db/migrate/20190620000010_add_subject.rb +8 -0
  66. data/spec/support/v5.2/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
  67. data/spec/support/v5.2/db/migrate/20190620000012_add_spell.rb +8 -0
  68. data/spec/support/v5.2/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
  69. data/spec/support/v5.2/db/migrate/20190620000014_create_headmasters.rb +9 -0
  70. data/spec/support/v5.2/db/schema.rb +28 -1
  71. data/spec/support/v5.2/lib/mythical_rbi_plugin.rb +1 -1
  72. data/spec/support/v5.2/sorbet_test_cases.rb +146 -42
  73. data/spec/support/v6.0/Gemfile.lock +8 -8
  74. data/spec/support/v6.0/app/models/headmaster.rb +8 -0
  75. data/spec/support/v6.0/app/models/school.rb +2 -0
  76. data/spec/support/v6.0/app/models/spell.rb +5 -0
  77. data/spec/support/v6.0/app/models/spell_book.rb +3 -0
  78. data/spec/support/v6.0/app/models/subject.rb +5 -0
  79. data/spec/support/v6.0/app/models/wizard.rb +6 -1
  80. data/spec/support/v6.0/db/migrate/20190620000010_add_subject.rb +8 -0
  81. data/spec/support/v6.0/db/migrate/20190620000011_add_subjects_wizards.rb +8 -0
  82. data/spec/support/v6.0/db/migrate/20190620000012_add_spell.rb +8 -0
  83. data/spec/support/v6.0/db/migrate/20190620000013_add_spells_spell_books.rb +8 -0
  84. data/spec/support/v6.0/db/migrate/20190620000014_create_headmasters.rb +9 -0
  85. data/spec/support/v6.0/db/schema.rb +28 -1
  86. data/spec/support/v6.0/lib/mythical_rbi_plugin.rb +1 -1
  87. data/spec/support/v6.0/sorbet_test_cases.rb +146 -42
  88. data/spec/test_data/v5.0/expected_active_record_base.rbi +113 -0
  89. data/spec/test_data/v5.0/expected_active_record_relation.rbi +199 -0
  90. data/spec/test_data/v5.0/expected_habtm_subjects.rbi +660 -0
  91. data/spec/test_data/v5.0/expected_habtm_wizards.rbi +660 -0
  92. data/spec/test_data/v5.0/expected_headmaster.rbi +452 -0
  93. data/spec/test_data/v5.0/expected_internal_metadata.rbi +0 -217
  94. data/spec/test_data/v5.0/expected_potion.rbi +0 -217
  95. data/spec/test_data/v5.0/expected_robe.rbi +0 -217
  96. data/spec/test_data/v5.0/expected_schema_migration.rbi +0 -217
  97. data/spec/test_data/v5.0/expected_school.rbi +11 -217
  98. data/spec/test_data/v5.0/expected_spell.rbi +440 -0
  99. data/spec/test_data/v5.0/expected_spell/habtm_spell_books.rbi +443 -0
  100. data/spec/test_data/v5.0/expected_spell_book.rbi +14 -222
  101. data/spec/test_data/v5.0/expected_spell_book/habtm_spell_books.rbi +637 -0
  102. data/spec/test_data/v5.0/expected_spell_book/habtm_spells.rbi +443 -0
  103. data/spec/test_data/v5.0/expected_squib.rbi +14 -219
  104. data/spec/test_data/v5.0/expected_subject.rbi +440 -0
  105. data/spec/test_data/v5.0/expected_subject/habtm_wizards.rbi +443 -0
  106. data/spec/test_data/v5.0/expected_wand.rbi +4 -221
  107. data/spec/test_data/v5.0/expected_wizard.rbi +36 -240
  108. data/spec/test_data/v5.0/expected_wizard/habtm_subjects.rbi +443 -0
  109. data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +30 -240
  110. data/spec/test_data/v5.1/expected_active_record_base.rbi +113 -0
  111. data/spec/test_data/v5.1/expected_active_record_relation.rbi +178 -0
  112. data/spec/test_data/v5.1/expected_habtm_subjects.rbi +672 -0
  113. data/spec/test_data/v5.1/expected_habtm_wizards.rbi +672 -0
  114. data/spec/test_data/v5.1/expected_headmaster.rbi +464 -0
  115. data/spec/test_data/v5.1/expected_internal_metadata.rbi +0 -217
  116. data/spec/test_data/v5.1/expected_potion.rbi +0 -217
  117. data/spec/test_data/v5.1/expected_robe.rbi +0 -217
  118. data/spec/test_data/v5.1/expected_schema_migration.rbi +0 -217
  119. data/spec/test_data/v5.1/expected_school.rbi +11 -217
  120. data/spec/test_data/v5.1/expected_spell.rbi +452 -0
  121. data/spec/test_data/v5.1/expected_spell/habtm_spell_books.rbi +455 -0
  122. data/spec/test_data/v5.1/expected_spell_book.rbi +14 -222
  123. data/spec/test_data/v5.1/expected_spell_book/habtm_spell_books.rbi +649 -0
  124. data/spec/test_data/v5.1/expected_spell_book/habtm_spells.rbi +455 -0
  125. data/spec/test_data/v5.1/expected_squib.rbi +14 -219
  126. data/spec/test_data/v5.1/expected_subject.rbi +452 -0
  127. data/spec/test_data/v5.1/expected_subject/habtm_wizards.rbi +455 -0
  128. data/spec/test_data/v5.1/expected_wand.rbi +4 -221
  129. data/spec/test_data/v5.1/expected_wizard.rbi +36 -240
  130. data/spec/test_data/v5.1/expected_wizard/habtm_subjects.rbi +455 -0
  131. data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +30 -240
  132. data/spec/test_data/v5.2/expected_active_record_base.rbi +113 -0
  133. data/spec/test_data/v5.2/expected_active_record_relation.rbi +175 -0
  134. data/spec/test_data/v5.2/expected_attachment.rbi +0 -217
  135. data/spec/test_data/v5.2/expected_blob.rbi +3 -217
  136. data/spec/test_data/v5.2/expected_habtm_subjects.rbi +672 -0
  137. data/spec/test_data/v5.2/expected_habtm_wizards.rbi +672 -0
  138. data/spec/test_data/v5.2/expected_headmaster.rbi +464 -0
  139. data/spec/test_data/v5.2/expected_internal_metadata.rbi +0 -217
  140. data/spec/test_data/v5.2/expected_potion.rbi +0 -217
  141. data/spec/test_data/v5.2/expected_robe.rbi +0 -217
  142. data/spec/test_data/v5.2/expected_schema_migration.rbi +0 -217
  143. data/spec/test_data/v5.2/expected_school.rbi +11 -217
  144. data/spec/test_data/v5.2/expected_spell.rbi +452 -0
  145. data/spec/test_data/v5.2/expected_spell/habtm_spell_books.rbi +455 -0
  146. data/spec/test_data/v5.2/expected_spell_book.rbi +14 -222
  147. data/spec/test_data/v5.2/expected_spell_book/habtm_spell_books.rbi +649 -0
  148. data/spec/test_data/v5.2/expected_spell_book/habtm_spells.rbi +455 -0
  149. data/spec/test_data/v5.2/expected_squib.rbi +20 -219
  150. data/spec/test_data/v5.2/expected_subject.rbi +452 -0
  151. data/spec/test_data/v5.2/expected_subject/habtm_wizards.rbi +455 -0
  152. data/spec/test_data/v5.2/expected_wand.rbi +4 -221
  153. data/spec/test_data/v5.2/expected_wizard.rbi +42 -240
  154. data/spec/test_data/v5.2/expected_wizard/habtm_subjects.rbi +455 -0
  155. data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +36 -240
  156. data/spec/test_data/v6.0/expected_active_record_base.rbi +113 -0
  157. data/spec/test_data/v6.0/expected_active_record_relation.rbi +175 -0
  158. data/spec/test_data/v6.0/expected_attachment.rbi +0 -217
  159. data/spec/test_data/v6.0/expected_blob.rbi +3 -217
  160. data/spec/test_data/v6.0/expected_habtm_subjects.rbi +720 -0
  161. data/spec/test_data/v6.0/expected_habtm_wizards.rbi +720 -0
  162. data/spec/test_data/v6.0/expected_headmaster.rbi +512 -0
  163. data/spec/test_data/v6.0/expected_internal_metadata.rbi +0 -217
  164. data/spec/test_data/v6.0/expected_potion.rbi +0 -217
  165. data/spec/test_data/v6.0/expected_robe.rbi +0 -217
  166. data/spec/test_data/v6.0/expected_schema_migration.rbi +0 -217
  167. data/spec/test_data/v6.0/expected_school.rbi +11 -217
  168. data/spec/test_data/v6.0/expected_spell.rbi +500 -0
  169. data/spec/test_data/v6.0/expected_spell/habtm_spell_books.rbi +503 -0
  170. data/spec/test_data/v6.0/expected_spell_book.rbi +14 -222
  171. data/spec/test_data/v6.0/expected_spell_book/habtm_spell_books.rbi +697 -0
  172. data/spec/test_data/v6.0/expected_spell_book/habtm_spells.rbi +503 -0
  173. data/spec/test_data/v6.0/expected_squib.rbi +20 -219
  174. data/spec/test_data/v6.0/expected_subject.rbi +500 -0
  175. data/spec/test_data/v6.0/expected_subject/habtm_wizards.rbi +503 -0
  176. data/spec/test_data/v6.0/expected_wand.rbi +4 -221
  177. data/spec/test_data/v6.0/expected_wizard.rbi +42 -240
  178. data/spec/test_data/v6.0/expected_wizard/habtm_subjects.rbi +503 -0
  179. data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +36 -240
  180. metadata +169 -14
  181. data/lib/bundled_rbi/active_record_base.rbi +0 -83
  182. data/lib/bundled_rbi/active_record_relation.rbi +0 -122
  183. data/lib/sorbet-rails/model_plugins/active_record_finder_methods.rb +0 -131
  184. data/spec/support/v5.0/typed-override.yaml +0 -2
  185. data/spec/support/v5.1/typed-override.yaml +0 -2
  186. data/spec/support/v5.2/typed-override.yaml +0 -2
  187. data/spec/support/v6.0/typed-override.yaml +0 -2
@@ -12,8 +12,9 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
12
12
  sig { returns(String) }
13
13
  def to_s
14
14
  type = base_type.to_s
15
- type = "T.nilable(#{type})" if nilable
15
+ # A nullable array column should be T.nilable(T::Array[column_type]) not T::Array[T.nilable(column_type)]
16
16
  type = "T::Array[#{type}]" if array_type
17
+ type = "T.nilable(#{type})" if nilable
17
18
  type
18
19
  end
19
20
  end
@@ -82,6 +83,7 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
82
83
  column_def
83
84
  )
84
85
  should_skip_setter_getter = false
86
+ nilable_column = nilable_column?(column_def)
85
87
 
86
88
  if @model_class.is_a?(::ActiveRecord::Enum)
87
89
  config = @model_class.typed_enum_reflections[column_name]
@@ -89,19 +91,19 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
89
91
  # do not generate the methods for enums in strict_mode
90
92
  should_skip_setter_getter = config.strict_mode
91
93
 
92
- t_enum_type = "#{@model_class.name}::#{config.class_name}"
94
+ t_enum_type = "#{model_class_name}::#{config.class_name}"
93
95
 
94
96
  # define T::Enum class & values
95
97
  enum_values = T.must(model_defined_enums[column_name])
96
98
  t_enum_values = @model_class.gen_typed_enum_values(enum_values.keys)
97
99
  root.create_enum_class(
98
100
  t_enum_type,
99
- enums: t_enum_values.map { |k, v| [v, "'#{k}'"] },
101
+ enums: t_enum_values.map { |k, v| [v, "%q{#{k}}"] },
100
102
  )
101
103
 
102
104
  # define t_enum setter/getter
103
105
  assignable_type = t_enum_type
104
- assignable_type = "T.nilable(#{assignable_type})" if column_def.null
106
+ assignable_type = "T.nilable(#{assignable_type})" if nilable_column
105
107
  # add directly to model_class_rbi because they are included
106
108
  # by sorbet's hidden.rbi
107
109
  model_class_rbi.create_method(
@@ -121,9 +123,9 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
121
123
  if !should_skip_setter_getter
122
124
  # enum attribute is treated differently
123
125
  assignable_type = "T.any(Integer, String, Symbol)"
124
- assignable_type = "T.nilable(#{assignable_type})" if column_def.null
126
+ assignable_type = "T.nilable(#{assignable_type})" if nilable_column
125
127
  return_type = "String"
126
- return_type = "T.nilable(#{return_type})" if column_def.null
128
+ return_type = "T.nilable(#{return_type})" if nilable_column
127
129
 
128
130
  attribute_module_rbi.create_method(
129
131
  column_name.to_s,
@@ -145,6 +147,11 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
145
147
  ActiveRecord::Base.connection.lookup_cast_type_from_column(column_def) :
146
148
  column_def.cast_type
147
149
 
150
+ array_type = false
151
+ if column_def.try(:array?)
152
+ cast_type = cast_type.subtype if cast_type.try(:subtype)
153
+ array_type = true
154
+ end
148
155
  strict_type =
149
156
  active_record_type_to_sorbet_type(
150
157
  cast_type,
@@ -153,8 +160,8 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
153
160
 
154
161
  ColumnType.new(
155
162
  base_type: strict_type,
156
- nilable: column_def.null,
157
- array_type: column_def.try(:array?),
163
+ nilable: nilable_column?(column_def),
164
+ array_type: array_type,
158
165
  )
159
166
  end
160
167
 
@@ -214,6 +221,11 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
214
221
  )
215
222
  end
216
223
 
224
+ sig { params(column_def: T.untyped).returns(T::Boolean) }
225
+ def nilable_column?(column_def)
226
+ !!(column_def.null && !attribute_has_unconditional_presence_validation?(column_def.name))
227
+ end
228
+
217
229
  sig { params(column_type: ColumnType).returns(String) }
218
230
  def value_type_for_attr_writer(column_type)
219
231
  # it's safe - and convenient - to assign any "time like" object to a time zone
@@ -29,5 +29,14 @@ 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
32
41
  end
33
42
  end
@@ -4,16 +4,7 @@ class SorbetRails::ModelPlugins::EnumerableCollections < SorbetRails::ModelPlugi
4
4
 
5
5
  sig { override.params(root: Parlour::RbiGenerator::Namespace).void }
6
6
  def generate(root)
7
- # model relation, assocation relation, & association proxy are enumerable
8
- # we need to implement "each" in these methods so that they work
9
- model_relation_class_rbi = root.create_class(self.model_relation_class_name)
10
- create_enumerable_methods_for(model_relation_class_rbi, include_methods: false)
11
-
12
- model_assoc_relation_rbi = root.create_class(self.model_assoc_relation_class_name)
13
- create_enumerable_methods_for(model_assoc_relation_rbi)
14
-
15
7
  model_assoc_proxy_class_rbi = root.create_class(self.model_assoc_proxy_class_name)
16
- create_enumerable_methods_for(model_assoc_proxy_class_rbi)
17
8
 
18
9
  # following methods only exists in an association proxy
19
10
  ["<<", "append", "push", "concat"].each do |method_name|
@@ -27,45 +18,4 @@ class SorbetRails::ModelPlugins::EnumerableCollections < SorbetRails::ModelPlugi
27
18
  )
28
19
  end
29
20
  end
30
-
31
- sig { params(class_rbi: Parlour::RbiGenerator::ClassNamespace, include_methods: T::Boolean).void }
32
- def create_enumerable_methods_for(class_rbi, include_methods: true)
33
- class_rbi.create_include("Enumerable")
34
-
35
- # An escape hatch that prevents these methods from being added unnecessarily.
36
- # In certain cases, we can remove these from the classes because they can
37
- # be generalized to use Elem in a superclass instead.
38
- if include_methods
39
- class_rbi.create_method(
40
- "each",
41
- parameters: [
42
- Parameter.new("&block", type: "T.proc.params(e: #{self.model_class_name}).void")
43
- ],
44
- return_type: "T::Array[#{self.model_class_name}]",
45
- implementation: true,
46
- )
47
- class_rbi.create_method(
48
- "flatten",
49
- parameters: [ Parameter.new("level", type: "T.nilable(Integer)") ],
50
- return_type: "T::Array[#{self.model_class_name}]",
51
- )
52
- # this is an escape hatch when there are conflicts in signatures of Enumerable & ActiveRecord
53
- class_rbi.create_method(
54
- "to_a",
55
- return_type: "T::Array[#{self.model_class_name}]",
56
- )
57
- # TODO use type_parameters(:U) when parlour supports it
58
- class_rbi.create_arbitrary(
59
- code: <<~RUBY
60
- sig do
61
- type_parameters(:U).params(
62
- blk: T.proc.params(arg0: Elem).returns(T.type_parameter(:U)),
63
- )
64
- .returns(T::Array[T.type_parameter(:U)])
65
- end
66
- def map(&blk); end
67
- RUBY
68
- )
69
- end
70
- end
71
21
  end
@@ -6,7 +6,6 @@ require('sorbet-rails/model_plugins/active_relation_where_not')
6
6
  require('sorbet-rails/model_plugins/active_record_named_scope')
7
7
  require('sorbet-rails/model_plugins/active_record_attribute')
8
8
  require('sorbet-rails/model_plugins/active_record_assoc')
9
- require('sorbet-rails/model_plugins/active_record_finder_methods')
10
9
  require('sorbet-rails/model_plugins/custom_finder_methods')
11
10
  require('sorbet-rails/model_plugins/enumerable_collections')
12
11
  require('sorbet-rails/model_plugins/active_storage_methods')
@@ -52,8 +51,6 @@ module SorbetRails::ModelPlugins
52
51
  ActiveRecordAttribute
53
52
  when :active_record_assoc
54
53
  ActiveRecordAssoc
55
- when :active_record_finder_methods
56
- ActiveRecordFinderMethods
57
54
  when :custom_finder_methods
58
55
  CustomFinderMethods
59
56
  when :enumerable_collections
@@ -28,13 +28,13 @@ class SorbetRails::ModelRbiFormatter
28
28
  # Load all dynamic instance methods of this model by instantiating a fake model
29
29
  @model_class.new unless @model_class.abstract_class?
30
30
  rescue StandardError => err
31
- puts "#{err.class}: Note: Unable to create new instance of #{model_class.name}"
31
+ puts "#{err.class}: Note: Unable to create new instance of #{model_class_name}"
32
32
  end
33
33
  end
34
34
 
35
35
  sig {returns(String)}
36
36
  def generate_rbi
37
- puts "-- Generate sigs for #{@model_class.name} --"
37
+ puts "-- Generate sigs for #{model_class_name} --"
38
38
 
39
39
  # Collect the instances of each plugin into an array
40
40
  plugin_instances = self.class.get_plugins.map do |plugin_klass|
@@ -5,27 +5,36 @@ module SorbetRails::ModelUtils
5
5
 
6
6
  abstract!
7
7
 
8
- sig { abstract.returns(T.class_of(ActiveRecord::Base)) }
8
+ # if we're a HABTM class then model_class is an anonymous class (see the rails link below) and
9
+ # 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))) }
9
11
  def model_class; end
10
12
 
13
+ sig { returns(T::Boolean) }
14
+ def habtm_class?
15
+ # checking the class name seems to be the cleanest way to figure this out, see:
16
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb#L54
17
+ T.must(model_class.name).start_with?('HABTM_')
18
+ end
19
+
11
20
  sig { returns(String) }
12
21
  def model_class_name
13
- "#{model_class.name}"
22
+ model_class.to_s
14
23
  end
15
24
 
16
25
  sig { returns(String) }
17
26
  def model_relation_class_name
18
- "#{model_class.name}::ActiveRecord_Relation"
27
+ "#{model_class_name}::ActiveRecord_Relation"
19
28
  end
20
29
 
21
30
  sig { returns(String) }
22
31
  def model_assoc_proxy_class_name
23
- "#{model_class.name}::ActiveRecord_Associations_CollectionProxy"
32
+ "#{model_class_name}::ActiveRecord_Associations_CollectionProxy"
24
33
  end
25
34
 
26
35
  sig { returns(String) }
27
36
  def model_assoc_relation_class_name
28
- "#{model_class.name}::ActiveRecord_AssociationRelation"
37
+ "#{model_class_name}::ActiveRecord_AssociationRelation"
29
38
  end
30
39
 
31
40
  sig { returns(String) }
@@ -46,7 +55,7 @@ module SorbetRails::ModelUtils
46
55
 
47
56
  sig { params(module_name: String).returns(String) }
48
57
  def model_module_name(module_name)
49
- "#{model_class.name}::#{module_name}"
58
+ "#{model_class_name}::#{module_name}"
50
59
  end
51
60
 
52
61
  sig { params(method_name: T.any(String, Symbol)).returns(T::Boolean) }
@@ -1,5 +1,6 @@
1
1
  # typed: false
2
2
  require 'sorbet-runtime'
3
+ require('sorbet-rails/deprecation.rb')
3
4
 
4
5
  module SorbetRails::CustomParamsMethods
5
6
  include Kernel
@@ -16,6 +17,11 @@ module SorbetRails::CustomParamsMethods
16
17
  returns(T.type_parameter(:U))
17
18
  }
18
19
  def require_typed(key, ta)
20
+ SorbetRails::TypeAssertDeprecation.deprecation_warning(
21
+ :require_typed,
22
+ 'Use TypedParams with a T::Struct represents the type of each parameters.'
23
+ )
24
+
19
25
  val = require(key)
20
26
  ta.assert(val)
21
27
  rescue TypeError
@@ -35,6 +41,11 @@ module SorbetRails::CustomParamsMethods
35
41
  returns(T.type_parameter(:U))
36
42
  }
37
43
  def fetch_typed(key, ta, *args)
44
+ SorbetRails::TypeAssertDeprecation.deprecation_warning(
45
+ :fetch_typed,
46
+ 'Use TypedParams with a T::Struct represents the type of each parameters.'
47
+ )
48
+
38
49
  val = fetch(key, *args)
39
50
  ta.assert(val)
40
51
  rescue TypeError
@@ -1,3 +1,4 @@
1
+ require("sorbet-rails/active_record_rbi_formatter")
1
2
  require("sorbet-rails/model_rbi_formatter")
2
3
  require("sorbet-rails/routes_rbi_formatter")
3
4
  require("sorbet-rails/helper_rbi_formatter")
@@ -39,9 +40,23 @@ namespace :rails_rbi do
39
40
  copy_bundled_rbi('type_assert.rbi')
40
41
  copy_bundled_rbi('parameters.rbi')
41
42
  copy_bundled_rbi('pluck_to_tstruct.rbi')
42
- copy_bundled_rbi('active_record_relation.rbi')
43
- copy_bundled_rbi('active_record_base.rbi')
44
43
  copy_bundled_rbi('typed_params.rbi')
44
+
45
+ # These files were previously bundled_rbi but are now generated so this
46
+ # is needed for backwards compatibility with anyone using `rails_rbi:custom`
47
+ Rake::Task['rails_rbi:active_record'].invoke
48
+ end
49
+
50
+ desc "Generate rbis for rails mailers"
51
+ task :active_record, [:root_dir] => :environment do |t, args|
52
+ formatter = SorbetRails::ActiveRecordRbiFormatter.new
53
+ FileUtils.mkdir_p(Rails.root.join("sorbet", "rails-rbi"))
54
+
55
+ file_path = Rails.root.join("sorbet", "rails-rbi", "active_record_base.rbi")
56
+ File.write(file_path, formatter.generate_active_record_base_rbi)
57
+
58
+ file_path = Rails.root.join("sorbet", "rails-rbi", "active_record_relation.rbi")
59
+ File.write(file_path, formatter.generate_active_record_relation_rbi)
45
60
  end
46
61
 
47
62
  desc "Generate rbis for rails models. Pass models name to regenerate rbi for only the given models."
@@ -133,12 +148,13 @@ namespace :rails_rbi do
133
148
  def generate_rbis_for_models(model_classes, available_classes)
134
149
  available_class_names = Set.new(available_classes.map { |c| c.name })
135
150
  formatted = model_classes.map do |model_class|
151
+ model_class_name = model_class.to_s
136
152
  begin
137
153
  formatter = SorbetRails::ModelRbiFormatter.new(model_class, available_class_names)
138
- [model_class.name, formatter.generate_rbi]
154
+ [model_class_name, formatter.generate_rbi]
139
155
  rescue StandardError, NotImplementedError => ex
140
156
  puts "---"
141
- puts "Error when handling model #{model_class.name}: #{ex}"
157
+ puts "Error when handling model #{model_class_name}: #{ex}"
142
158
  nil
143
159
  end
144
160
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{sorbet-rails}
3
- s.version = "0.6.2"
3
+ s.version = "0.6.3"
4
4
  s.date = %q{2019-04-18}
5
5
  s.summary = %q{Set of tools to make Sorbet work with Rails seamlessly.}
6
6
  s.authors = ["Chan Zuckerberg Initiative"]
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
 
18
18
  s.add_dependency 'parlour', '~> 2.0'
19
19
  s.add_dependency 'sorbet-runtime', '>= 0.5'
20
- s.add_dependency 'sorbet-coerce', '>= 0.2.4'
20
+ s.add_dependency 'sorbet-coerce', '>= 0.2.6'
21
21
  s.add_dependency 'method_source', '>= 0.9.2'
22
22
  s.add_dependency 'parser', '>= 2.7'
23
23
 
@@ -0,0 +1,24 @@
1
+ require 'rails_helper'
2
+ require 'sorbet-rails/active_record_rbi_formatter'
3
+
4
+ RSpec.describe SorbetRails::ActiveRecordRbiFormatter do
5
+ describe 'generate_active_record_base_rbi' do
6
+ it 'returns the expected rbi' do
7
+ formatter = SorbetRails::ActiveRecordRbiFormatter.new
8
+ expect_match_file(
9
+ formatter.generate_active_record_base_rbi,
10
+ 'expected_active_record_base.rbi'
11
+ )
12
+ end
13
+ end
14
+
15
+ describe 'generate_active_record_relation_rbi' do
16
+ it 'returns the expected rbi' do
17
+ formatter = SorbetRails::ActiveRecordRbiFormatter.new
18
+ expect_match_file(
19
+ formatter.generate_active_record_relation_rbi,
20
+ 'expected_active_record_relation.rbi'
21
+ )
22
+ end
23
+ end
24
+ end
@@ -51,7 +51,7 @@ def create_lib
51
51
  model_class_rbi.create_method(
52
52
  'mythicals',
53
53
  class_method: true,
54
- return_type: "T::Array[#{@model_class.name}]",
54
+ return_type: "T::Array[#{model_class_name}]",
55
55
  )
56
56
  end
57
57
  end
@@ -83,6 +83,9 @@ def create_models
83
83
  # simulate when belongs_to is optional by default, but it is enforced at the DB level
84
84
  belongs_to :wizard, optional: true
85
85
 
86
+ # habtm enforced at the DB level
87
+ has_and_belongs_to_many :spells
88
+
86
89
  enum book_type: {
87
90
  unclassified: 0,
88
91
  biology: 1,
@@ -129,6 +132,8 @@ def create_models
129
132
  file "app/models/wizard.rb", <<~RUBY
130
133
  class Wizard < ApplicationRecord
131
134
  validates :name, length: { minimum: 5 }, presence: true
135
+ # simulate conditional validation
136
+ validates :parent_email, presence: true, if: -> { false }
132
137
 
133
138
  typed_enum house: {
134
139
  Gryffindor: 0,
@@ -146,6 +151,7 @@ def create_models
146
151
  "Pomona Sprout": 2,
147
152
  "Filius Flitwick": 3,
148
153
  "Hagrid": 4,
154
+ "Alastor 'Mad-Eye' Moody": 5,
149
155
  }
150
156
 
151
157
  typed_enum broom: {
@@ -174,6 +180,8 @@ def create_models
174
180
 
175
181
  has_one :wand
176
182
  has_many :spell_books
183
+ # habtm which is optional at the db level
184
+ has_and_belongs_to_many :subjects
177
185
 
178
186
  # simulate when belongs_to is optional by default
179
187
  belongs_to :school, optional: true
@@ -212,8 +220,35 @@ def create_models
212
220
 
213
221
  file "app/models/school.rb", <<~RUBY
214
222
  class School < ApplicationRecord
223
+ has_one :headmaster
224
+ validates :headmaster, presence: true
225
+ end
226
+ RUBY
227
+
228
+ file "app/models/subject.rb", <<~RUBY
229
+ class Subject < ApplicationRecord
230
+ # habtm which is optional at the db level
231
+ has_and_belongs_to_many :wizards
232
+ end
233
+ RUBY
234
+
235
+ file "app/models/spell.rb", <<~RUBY
236
+ class Spell < ApplicationRecord
237
+ # habtm enforced at the DB level
238
+ has_and_belongs_to_many :spell_books
239
+ end
240
+ RUBY
241
+
242
+ file "app/models/headmaster.rb", <<~RUBY
243
+ class Headmaster < ApplicationRecord
244
+ belongs_to :school, required: false
245
+ belongs_to :wizard, optional: true
246
+
247
+ validates :school, presence: true
248
+ validates :wizard_id, presence: true
215
249
  end
216
250
  RUBY
251
+
217
252
  end
218
253
 
219
254
  def create_migrations
@@ -328,6 +363,58 @@ def create_migrations
328
363
  end
329
364
  end
330
365
  RUBY
366
+
367
+ file "db/migrate/20190620000010_add_subject.rb", <<~RUBY
368
+ class AddSubject < #{migration_superclass}
369
+ def change
370
+ create_table :subjects do |t|
371
+ t.string :name
372
+ end
373
+ end
374
+ end
375
+ RUBY
376
+
377
+ file "db/migrate/20190620000011_add_subjects_wizards.rb", <<~RUBY
378
+ class AddSubjectsWizards < #{migration_superclass}
379
+ def change
380
+ create_join_table :subjects, :wizards, column_options: { null: true } do |t|
381
+ t.index [:subject_id, :wizard_id]
382
+ end
383
+ end
384
+ end
385
+ RUBY
386
+
387
+ file "db/migrate/20190620000012_add_spell.rb", <<~RUBY
388
+ class AddSpell < #{migration_superclass}
389
+ def change
390
+ create_table :spells do |t|
391
+ t.string :name
392
+ end
393
+ end
394
+ end
395
+ RUBY
396
+
397
+ file "db/migrate/20190620000013_add_spells_spell_books.rb", <<~RUBY
398
+ class AddSpellsSpellBooks < #{migration_superclass}
399
+ def change
400
+ create_join_table :spells, :spell_books do |t|
401
+ t.index [:spell_id, :spell_book_id]
402
+ end
403
+ end
404
+ end
405
+ RUBY
406
+
407
+ file "db/migrate/20190620000014_create_headmasters.rb", <<~RUBY
408
+ class CreateHeadmasters < #{migration_superclass}
409
+ def change
410
+ create_table :headmasters do |t|
411
+ t.references :school
412
+ t.references :wizard
413
+ end
414
+ end
415
+ end
416
+ RUBY
417
+
331
418
  end
332
419
 
333
420
  def create_mailers