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
@@ -35,19 +35,19 @@ namespace :rails_rbi do
35
35
  File.write(file_path, inspector.format(SorbetRails::RoutesRbiFormatter.new))
36
36
  end
37
37
 
38
- desc "Copy custom rbis for ActionController::Parameters, pluck_to_struct, etc."
38
+ desc "Copy custom rbis for typed_params, pluck_to_struct, etc."
39
39
  task custom: :environment do
40
40
  copy_bundled_rbi('type_assert.rbi')
41
- copy_bundled_rbi('parameters.rbi')
42
41
  copy_bundled_rbi('pluck_to_tstruct.rbi')
43
42
  copy_bundled_rbi('typed_params.rbi')
43
+ copy_bundled_rbi('typed_enum.rbi')
44
44
 
45
45
  # These files were previously bundled_rbi but are now generated so this
46
46
  # is needed for backwards compatibility with anyone using `rails_rbi:custom`
47
47
  Rake::Task['rails_rbi:active_record'].invoke
48
48
  end
49
49
 
50
- desc "Generate rbis for rails mailers"
50
+ desc "Generate rbis for rails active_record base"
51
51
  task :active_record, [:root_dir] => :environment do |t, args|
52
52
  formatter = SorbetRails::ActiveRecordRbiFormatter.new
53
53
  FileUtils.mkdir_p(Rails.root.join("sorbet", "rails-rbi"))
@@ -77,11 +77,19 @@ namespace :rails_rbi do
77
77
  models_to_generate = models_to_generate - blacklisted_models
78
78
  end
79
79
 
80
- generated_rbis = generate_rbis_for_models(models_to_generate, all_models)
81
- generated_rbis.each do |model_name, contents|
82
- file_path = Rails.root.join("sorbet", "rails-rbi", "models", "#{model_name.underscore}.rbi")
83
- FileUtils.mkdir_p(File.dirname(file_path))
84
- File.write(file_path, contents)
80
+ available_class_names = Set.new(all_models.map { |c| c.name })
81
+ models_to_generate.each do |model_class|
82
+ model_class_name = model_class.to_s
83
+ begin
84
+ formatter = SorbetRails::ModelRbiFormatter.new(model_class, available_class_names)
85
+ file_path = Rails.root.join("sorbet", "rails-rbi", "models", "#{model_class_name.underscore}.rbi")
86
+ FileUtils.mkdir_p(File.dirname(file_path))
87
+ File.write(file_path, formatter.generate_rbi)
88
+ rescue StandardError, NotImplementedError => ex
89
+ puts "---"
90
+ puts "Error when handling model #{model_class_name}: #{ex}"
91
+ nil
92
+ end
85
93
  end
86
94
  end
87
95
 
@@ -121,7 +129,7 @@ namespace :rails_rbi do
121
129
  "mailers",
122
130
  "#{mailer_class.name.underscore}.rbi",
123
131
  )
124
- formatter = SorbetRails::MailerRbiFormatter.new(mailer_class)
132
+ formatter = ::SorbetRails.config.mailer_generator_class.new(mailer_class)
125
133
  FileUtils.mkdir_p(File.dirname(file_path))
126
134
  File.write(file_path, formatter.generate_rbi)
127
135
  end
@@ -139,28 +147,12 @@ namespace :rails_rbi do
139
147
  "jobs",
140
148
  "#{job_class.name.underscore}.rbi",
141
149
  )
142
- formatter = SorbetRails::JobRbiFormatter.new(job_class)
150
+ formatter = ::SorbetRails.config.job_generator_class.new(job_class)
143
151
  FileUtils.mkdir_p(File.dirname(file_path))
144
152
  File.write(file_path, formatter.generate_rbi)
145
153
  end
146
154
  end
147
155
 
148
- def generate_rbis_for_models(model_classes, available_classes)
149
- available_class_names = Set.new(available_classes.map { |c| c.name })
150
- formatted = model_classes.map do |model_class|
151
- model_class_name = model_class.to_s
152
- begin
153
- formatter = SorbetRails::ModelRbiFormatter.new(model_class, available_class_names)
154
- [model_class_name, formatter.generate_rbi]
155
- rescue StandardError, NotImplementedError => ex
156
- puts "---"
157
- puts "Error when handling model #{model_class_name}: #{ex}"
158
- nil
159
- end
160
- end
161
- Hash[formatted.compact] # remove models with errors
162
- end
163
-
164
156
  def blacklisted_models
165
157
  blacklisted_models = []
166
158
  blacklisted_models << ApplicationRecord if defined?(ApplicationRecord)
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{sorbet-rails}
3
- s.version = "0.6.3"
3
+ s.version = "0.7.1"
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"]
@@ -91,6 +91,8 @@ def create_models
91
91
  biology: 1,
92
92
  dark_art: 999,
93
93
  }
94
+
95
+ scope :recent, -> { where('created_at > ?', 1.month.ago) }
94
96
  end
95
97
  RUBY
96
98
 
@@ -133,7 +135,7 @@ def create_models
133
135
  class Wizard < ApplicationRecord
134
136
  validates :name, length: { minimum: 5 }, presence: true
135
137
  # simulate conditional validation
136
- validates :parent_email, presence: true, if: -> { false }
138
+ validates :parent_email, presence: true, if: :Slytherin?
137
139
 
138
140
  typed_enum house: {
139
141
  Gryffindor: 0,
@@ -478,11 +480,6 @@ def create_jobs
478
480
  end
479
481
 
480
482
  def add_sorbet_test_files
481
- file "typed-override.yaml", <<~YAML
482
- true:
483
- - ./sorbet_test_cases.rb
484
- YAML
485
-
486
483
  copy_file "./sorbet_test_cases.rb", "sorbet_test_cases.rb"
487
484
  end
488
485
 
@@ -29,26 +29,6 @@ T.assert_type!(T.must(wizard.wand).wizard, Wizard)
29
29
  T.assert_type!(wizard.spell_books, SpellBook::ActiveRecord_Associations_CollectionProxy)
30
30
  T.assert_type!(wizard.spell_book_ids, T::Array[Integer])
31
31
 
32
- # -- model relation
33
- # default
34
- T.assert_type!(Wizard.all, Wizard::ActiveRecord_Relation)
35
-
36
- # custom scope
37
- T.assert_type!(Wizard.recent, Wizard::ActiveRecord_Relation)
38
-
39
- # enum scope
40
- T.assert_type!(Wizard.Gryffindor, Wizard::ActiveRecord_Relation)
41
-
42
- # ActiveRecord Querying
43
- T.assert_type!(Wizard.Gryffindor.recent, Wizard::ActiveRecord_Relation)
44
- T.assert_type!(Wizard.Gryffindor.recent.unscoped, Wizard::ActiveRecord_Relation)
45
- T.assert_type!(Wizard.where(id: 1), Wizard::ActiveRecord_Relation)
46
- T.assert_type!(Wizard.where(id: 1).recent, Wizard::ActiveRecord_Relation)
47
- T.assert_type!(Wizard.where.not(id: 1), Wizard::ActiveRecord_Relation)
48
- T.assert_type!(Wizard.preload(:spell_books), Wizard::ActiveRecord_Relation)
49
- T.assert_type!(Wizard.eager_load(:spell_books), Wizard::ActiveRecord_Relation)
50
- T.assert_type!(Wizard.order(:id), Wizard::ActiveRecord_Relation)
51
-
52
32
  # Finder methods -- Model
53
33
  T.assert_type!(Wizard.exists?(name: 'Test'), T::Boolean)
54
34
  T.assert_type!(Wizard.find(wizard.id), Wizard)
@@ -83,6 +63,8 @@ Wizard.find_each { |w| T.assert_type!(w, Wizard) }
83
63
  T.assert_type!(Wizard.find_each, T::Enumerator[Wizard])
84
64
  Wizard.find_in_batches { |w| T.assert_type!(w, T::Array[Wizard]) }
85
65
  T.assert_type!(Wizard.find_in_batches, T::Enumerator[T::Array[Wizard]])
66
+ Wizard.in_batches { |w| T.assert_type!(w, Wizard::ActiveRecord_Relation) }
67
+ T.assert_type!(Wizard.in_batches, ActiveRecord::Batches::BatchEnumerator)
86
68
  # T.assert_type!(Wizard.destroy_all, T::Array[Wizard]) # Ignored until we add support
87
69
  T.assert_type!(Wizard.any?, T::Boolean)
88
70
  T.assert_type!(Wizard.many?, T::Boolean)
@@ -90,6 +72,18 @@ T.assert_type!(Wizard.none?, T::Boolean)
90
72
  T.assert_type!(Wizard.one?, T::Boolean)
91
73
  # T.assert_type!(Wizard.update_all(name: 'Harry Potter'), Integer) # Ignored until we add support
92
74
  # T.assert_type!(Wizard.delete_all, Integer) # Ignored until we add support
75
+ # Query methods
76
+ T.assert_type!(Wizard.all, Wizard::ActiveRecord_Relation)
77
+ T.assert_type!(Wizard.recent, Wizard::ActiveRecord_Relation) # Named scope
78
+ T.assert_type!(Wizard.Gryffindor, Wizard::ActiveRecord_Relation) # Enum scope
79
+ T.assert_type!(Wizard.Gryffindor.recent, Wizard::ActiveRecord_Relation)
80
+ T.assert_type!(Wizard.Gryffindor.recent.unscoped, Wizard::ActiveRecord_Relation)
81
+ T.assert_type!(Wizard.where(id: 1), Wizard::ActiveRecord_Relation)
82
+ T.assert_type!(Wizard.where(id: 1).recent, Wizard::ActiveRecord_Relation)
83
+ T.assert_type!(Wizard.where.not(id: 1), Wizard::ActiveRecord_Relation)
84
+ T.assert_type!(Wizard.preload(:spell_books), Wizard::ActiveRecord_Relation)
85
+ T.assert_type!(Wizard.eager_load(:spell_books), Wizard::ActiveRecord_Relation)
86
+ T.assert_type!(Wizard.order(:id), Wizard::ActiveRecord_Relation)
93
87
 
94
88
  # Finder methods -- ActiveRecord::Relation
95
89
  T.assert_type!(Wizard.all.exists?(name: 'Harry Potter'), T::Boolean)
@@ -126,6 +120,8 @@ Wizard.all.find_each { |w| T.assert_type!(w, Wizard) }
126
120
  T.assert_type!(Wizard.all.find_each, T::Enumerator[Wizard])
127
121
  Wizard.all.find_in_batches { |w| T.assert_type!(w, T::Array[Wizard]) }
128
122
  T.assert_type!(Wizard.all.find_in_batches, T::Enumerator[T::Array[Wizard]])
123
+ Wizard.all.in_batches { |w| T.assert_type!(w, Wizard::ActiveRecord_Relation) }
124
+ T.assert_type!(Wizard.all.in_batches, T::Enumerable[Wizard::ActiveRecord_Relation])
129
125
  # T.assert_type!(Wizard.all.destroy_all, T::Array[Wizard]) # Ignored until we add support
130
126
  T.assert_type!(Wizard.all.any?, T::Boolean)
131
127
  T.assert_type!(Wizard.all.many?, T::Boolean)
@@ -133,6 +129,18 @@ T.assert_type!(Wizard.all.none?, T::Boolean)
133
129
  T.assert_type!(Wizard.all.one?, T::Boolean)
134
130
  # T.assert_type!(Wizard.all.update_all(name: 'Harry Potter'), Integer) # Ignored until we add support
135
131
  # T.assert_type!(Wizard.all.delete_all, Integer) # Ignored until we add support
132
+ # Query methods
133
+ T.assert_type!(Wizard.all.all, Wizard::ActiveRecord_Relation)
134
+ T.assert_type!(Wizard.all.recent, Wizard::ActiveRecord_Relation) # Named scope
135
+ T.assert_type!(Wizard.all.Gryffindor, Wizard::ActiveRecord_Relation) # Enum scope
136
+ T.assert_type!(Wizard.all.Gryffindor.recent, Wizard::ActiveRecord_Relation)
137
+ T.assert_type!(Wizard.all.Gryffindor.recent.unscoped, Wizard::ActiveRecord_Relation)
138
+ T.assert_type!(Wizard.all.where(id: 1), Wizard::ActiveRecord_Relation)
139
+ T.assert_type!(Wizard.all.where(id: 1).recent, Wizard::ActiveRecord_Relation)
140
+ T.assert_type!(Wizard.all.where.not(id: 1), Wizard::ActiveRecord_Relation)
141
+ T.assert_type!(Wizard.all.preload(:spell_books), Wizard::ActiveRecord_Relation)
142
+ T.assert_type!(Wizard.all.eager_load(:spell_books), Wizard::ActiveRecord_Relation)
143
+ T.assert_type!(Wizard.all.order(:id), Wizard::ActiveRecord_Relation)
136
144
  # Enumerable methods
137
145
  Wizard.all.each { |w| T.assert_type!(w, Wizard) }
138
146
  Wizard.all.map { |w| T.assert_type!(w, Wizard) }
@@ -172,10 +180,12 @@ T.assert_type!(spell_books.create!(name: 'Fantastic Beasts') { |s| T.assert_type
172
180
  T.assert_type!(spell_books.first_or_create(name: 'Fantastic Beasts') { |s| T.assert_type!(s, SpellBook) }, SpellBook)
173
181
  T.assert_type!(spell_books.first_or_create!(name: 'Fantastic Beasts') { |s| T.assert_type!(s, SpellBook) }, SpellBook)
174
182
  T.assert_type!(spell_books.first_or_initialize { |s| T.assert_type!(s, SpellBook) }, SpellBook)
175
- # spell_books.find_each { |s| T.assert_type!(s, SpellBook) } # TODO: Handle for Rails 6
176
- # T.assert_type!(spell_books.find_each, T::Enumerator[SpellBook]) # TODO: Handle for Rails 6
177
- # spell_books.find_in_batches { |s| T.assert_type!(s, T::Array[SpellBook]) } # TODO: Handle for Rails 6
178
- # T.assert_type!(spell_books.find_in_batches, T::Enumerator[T::Array[SpellBook]]) # TODO: Handle for Rails 6
183
+ spell_books.find_each { |s| T.assert_type!(s, SpellBook) }
184
+ T.assert_type!(spell_books.find_each, T::Enumerator[SpellBook])
185
+ spell_books.find_in_batches { |s| T.assert_type!(s, T::Array[SpellBook]) }
186
+ T.assert_type!(spell_books.find_in_batches, T::Enumerator[T::Array[SpellBook]])
187
+ spell_books.in_batches { |s| T.assert_type!(s, SpellBook::ActiveRecord_AssociationRelation) }
188
+ T.assert_type!(spell_books.in_batches, T::Enumerable[SpellBook::ActiveRecord_AssociationRelation])
179
189
  # T.assert_type!(spell_books.destroy_all, T::Array[SpellBook]) # Ignored until we add support
180
190
  T.assert_type!(spell_books.any?, T::Boolean)
181
191
  T.assert_type!(spell_books.many?, T::Boolean)
@@ -183,13 +193,18 @@ T.assert_type!(spell_books.none?, T::Boolean)
183
193
  T.assert_type!(spell_books.one?, T::Boolean)
184
194
  # T.assert_type!(spell_books.update_all(name: 'Fantastic Beasts'), Integer) # Ignored until we add support
185
195
  # T.assert_type!(spell_books.delete_all, Integer) # Ignored until we add support
186
- # CollectionProxy query also typed correctly!
196
+ # Query methods
197
+ T.assert_type!(spell_books.all, SpellBook::ActiveRecord_AssociationRelation)
198
+ T.assert_type!(spell_books.recent, SpellBook::ActiveRecord_AssociationRelation) # Named scope
199
+ T.assert_type!(spell_books.unclassified, SpellBook::ActiveRecord_AssociationRelation) # Enum scope
200
+ T.assert_type!(spell_books.unclassified.recent, SpellBook::ActiveRecord_AssociationRelation)
201
+ T.assert_type!(spell_books.unclassified.recent.unscoped, SpellBook::ActiveRecord_Relation) # Turns back into relation
187
202
  T.assert_type!(spell_books.where(id: 1), SpellBook::ActiveRecord_AssociationRelation)
203
+ T.assert_type!(spell_books.where(id: 1).recent, SpellBook::ActiveRecord_AssociationRelation)
204
+ T.assert_type!(spell_books.where.not(id: 1), SpellBook::ActiveRecord_AssociationRelation)
188
205
  T.assert_type!(spell_books.preload(:wizard), SpellBook::ActiveRecord_AssociationRelation)
189
206
  T.assert_type!(spell_books.eager_load(:wizard), SpellBook::ActiveRecord_AssociationRelation)
190
207
  T.assert_type!(spell_books.order(:id), SpellBook::ActiveRecord_AssociationRelation)
191
- T.assert_type!(spell_books.where.not(id: 1), SpellBook::ActiveRecord_AssociationRelation)
192
- T.assert_type!(spell_books.biology, SpellBook::ActiveRecord_AssociationRelation)
193
208
  # Enumerable methods
194
209
  spell_books.each { |s| T.assert_type!(s, SpellBook) }
195
210
  spell_books.map { |s| T.assert_type!(s, SpellBook) }
@@ -237,10 +252,12 @@ T.assert_type!(spell_books_query.create!(name: 'Fantastic Beasts') { |s| T.asser
237
252
  T.assert_type!(spell_books_query.first_or_create(name: 'Fantastic Beasts') { |s| T.assert_type!(s, SpellBook) }, SpellBook) # Ignored until we add support
238
253
  T.assert_type!(spell_books_query.first_or_create!(name: 'Fantastic Beasts') { |s| T.assert_type!(s, SpellBook) }, SpellBook) # Ignored until we add support
239
254
  T.assert_type!(spell_books_query.first_or_initialize { |s| T.assert_type!(s, SpellBook) }, SpellBook) # Ignored until we add support
240
- # spell_books_query.find_each { |s| T.assert_type!(s, SpellBook) } # TODO: Handle for Rails 6
241
- # T.assert_type!(spell_books_query.find_each, T::Enumerator[SpellBook]) # TODO: Handle for Rails 6
242
- # spell_books_query.find_in_batches { |s| T.assert_type!(s, T::Array[SpellBook]) } # TODO: Handle for Rails 6
243
- # T.assert_type!(spell_books_query.find_in_batches, T::Enumerator[T::Array[SpellBook]]) # TODO: Handle for Rails 6
255
+ spell_books_query.find_each { |s| T.assert_type!(s, SpellBook) }
256
+ T.assert_type!(spell_books_query.find_each, T::Enumerator[SpellBook])
257
+ spell_books_query.find_in_batches { |s| T.assert_type!(s, T::Array[SpellBook]) }
258
+ T.assert_type!(spell_books_query.find_in_batches, T::Enumerator[T::Array[SpellBook]])
259
+ spell_books_query.in_batches { |s| T.assert_type!(s, SpellBook::ActiveRecord_AssociationRelation) }
260
+ T.assert_type!(spell_books_query.in_batches, T::Enumerable[SpellBook::ActiveRecord_AssociationRelation])
244
261
  # T.assert_type!(spell_books_query.destroy_all, T::Array[SpellBook]) # Ignored until we add support
245
262
  T.assert_type!(spell_books_query.any?, T::Boolean)
246
263
  T.assert_type!(spell_books_query.many?, T::Boolean)
@@ -248,12 +265,18 @@ T.assert_type!(spell_books_query.none?, T::Boolean)
248
265
  T.assert_type!(spell_books_query.one?, T::Boolean)
249
266
  # T.assert_type!(spell_books_query.update_all(name: 'Fantastic Beasts'), Integer) # Ignored until we add support
250
267
  # T.assert_type!(spell_books_query.delete_all, Integer) # Ignored until we add support
251
- # Query chaining
268
+ # Query methods
269
+ T.assert_type!(spell_books_query.all, SpellBook::ActiveRecord_AssociationRelation)
270
+ T.assert_type!(spell_books_query.recent, SpellBook::ActiveRecord_AssociationRelation) # Named scope
271
+ T.assert_type!(spell_books_query.unclassified, SpellBook::ActiveRecord_AssociationRelation) # Enum scope
272
+ T.assert_type!(spell_books_query.unclassified.recent, SpellBook::ActiveRecord_AssociationRelation)
273
+ T.assert_type!(spell_books_query.unclassified.recent.unscoped, SpellBook::ActiveRecord_Relation) # Turns back into relation
274
+ T.assert_type!(spell_books_query.where(id: 1), SpellBook::ActiveRecord_AssociationRelation)
275
+ T.assert_type!(spell_books_query.where(id: 1).recent, SpellBook::ActiveRecord_AssociationRelation)
276
+ T.assert_type!(spell_books_query.where.not(id: 1), SpellBook::ActiveRecord_AssociationRelation)
252
277
  T.assert_type!(spell_books_query.preload(:wizard), SpellBook::ActiveRecord_AssociationRelation)
253
278
  T.assert_type!(spell_books_query.eager_load(:wizard), SpellBook::ActiveRecord_AssociationRelation)
254
279
  T.assert_type!(spell_books_query.order(:id), SpellBook::ActiveRecord_AssociationRelation)
255
- T.assert_type!(spell_books_query.where.not(id: 1), SpellBook::ActiveRecord_AssociationRelation)
256
- T.assert_type!(spell_books_query.biology, SpellBook::ActiveRecord_AssociationRelation)
257
280
  # Enumerable methods
258
281
  spell_books_query.each { |s| T.assert_type!(s, SpellBook) }
259
282
  spell_books_query.map { |s| T.assert_type!(s, SpellBook) }
@@ -338,44 +361,6 @@ params = ActionController::Parameters.new({
338
361
  typed_params = TypedParams[MyActionParams].new.extract!(params)
339
362
  T.assert_type!(typed_params, MyActionParams)
340
363
 
341
- # -- require_typed
342
- T.assert_type!(
343
- params.require_typed(:age, TA[Integer].new),
344
- Integer,
345
- )
346
- T.assert_type!(
347
- params.require_typed(:name, TA[String].new),
348
- String,
349
- )
350
- info = params.require_typed(:info, TA[ActionController::Parameters].new)
351
- T.assert_type!(info, ActionController::Parameters)
352
- T.assert_type!(
353
- info.require_typed(:friends, TA[T::Array[String]].new),
354
- T::Array[String],
355
- )
356
- # -- fetch_typed
357
- T.assert_type!(
358
- params.fetch_typed(:age, TA[Integer].new),
359
- Integer,
360
- )
361
- T.assert_type!(
362
- params.fetch_typed(:name, TA[String].new),
363
- String,
364
- )
365
- T.assert_type!(
366
- params.fetch_typed(:nonexistence, TA[String].new, ''),
367
- String,
368
- )
369
- T.assert_type!(
370
- params.fetch_typed(:nonexistence, TA[T.nilable(String)].new, nil),
371
- T.nilable(String),
372
- )
373
- T.assert_type!(
374
- params.fetch_typed(:nonexistence, TA[T::Array[Integer]].new, []),
375
- T::Array[Integer],
376
- )
377
-
378
-
379
364
  # -- pluck to tstruct
380
365
  class WizardStruct < T::Struct
381
366
  const :name, String
@@ -392,23 +377,16 @@ Wizard.all.pluck_to_tstruct(TA[WizardStruct].new).each do |row|
392
377
  T.assert_type!(row, WizardStruct)
393
378
  end
394
379
 
395
-
396
380
  # -- GeneratedUrlHelpers
397
381
  class TestHelper
398
382
  include GeneratedUrlHelpers
399
-
400
- # need to implement this for the url
401
- def default_url_options
402
- {
403
- protocol: 'http',
404
- host: 'localhost',
405
- port: 3000,
406
- }
407
- end
408
-
409
- def test_url_helper
410
- T.assert_type!(test_index_path, String)
411
- T.assert_type!(test_index_url, String)
412
- end
413
383
  end
414
- TestHelper.new.test_url_helper
384
+
385
+ T.assert_type!(TestHelper.new.test_index_path, String)
386
+ # need to set this config for _url methods
387
+ Rails.application.routes.default_url_options = {
388
+ protocol: 'http',
389
+ host: 'localhost',
390
+ port: 3000,
391
+ }
392
+ T.assert_type!(TestHelper.new.test_index_url, String)
@@ -1,5 +1,5 @@
1
1
  require 'rails_helper'
2
- require 'sorbet-rails/mailer_rbi_formatter'
2
+ require 'sorbet-rails/job_rbi_formatter'
3
3
 
4
4
  RSpec.describe SorbetRails::MailerRbiFormatter do
5
5
 
@@ -9,6 +9,18 @@ RSpec.describe SorbetRails::PluckToTStruct do
9
9
  )
10
10
  end
11
11
 
12
+ let!(:harrys_wand) do
13
+ Wand.create!(
14
+ wizard: harry,
15
+ core_type: :phoenix_feather,
16
+ wood_type: "Holly",
17
+ chosen_at_date: Date.parse('2019-09-01'),
18
+ chosen_at_time: Time.parse('2019-09-01T09:00:00Z'),
19
+ broken: true,
20
+ broken_at: Time.parse('2019-09-05T15:30:00Z'),
21
+ )
22
+ end
23
+
12
24
  let!(:hermione) do
13
25
  Wizard.create!(
14
26
  name: 'Hermione Granger',
@@ -16,6 +28,18 @@ RSpec.describe SorbetRails::PluckToTStruct do
16
28
  )
17
29
  end
18
30
 
31
+ let!(:hermiones_wand) do
32
+ Wand.create!(
33
+ wizard: hermione,
34
+ core_type: :phoenix_feather,
35
+ wood_type: "Vine",
36
+ chosen_at_date: Date.parse('2019-09-01'),
37
+ chosen_at_time: Time.parse('2019-09-01T09:00:00Z'),
38
+ broken: true,
39
+ broken_at: Time.parse('2019-09-05T15:30:00Z'),
40
+ )
41
+ end
42
+
19
43
  class WizardName < T::Struct
20
44
  const :name, String
21
45
 
@@ -43,6 +67,21 @@ RSpec.describe SorbetRails::PluckToTStruct do
43
67
  end
44
68
  end
45
69
 
70
+ class WizardWithWandT < T::Struct
71
+ const :name, String
72
+ const :house, String
73
+ const :wand_wood_type, String
74
+
75
+ def ==(other)
76
+ return false unless other.is_a?(self.class)
77
+ name == other.name && house == other.house
78
+ end
79
+
80
+ def eql?(other)
81
+ self == other
82
+ end
83
+ end
84
+
46
85
  shared_examples 'pluck_to_tstruct' do |struct_type, expected_values|
47
86
  it 'plucks correctly from ActiveRecord model' do
48
87
  plucked = Wizard.pluck_to_tstruct(TA[struct_type].new)
@@ -55,6 +94,18 @@ RSpec.describe SorbetRails::PluckToTStruct do
55
94
  end
56
95
  end
57
96
 
97
+ shared_examples 'pluck_to_tstruct with associations' do |struct_type, associations, expected_values|
98
+ it 'plucks correctly from ActiveRecord model with associations' do
99
+ plucked = Wizard.joins(:wand).pluck_to_tstruct(TA[struct_type].new, associations: associations)
100
+ expect(plucked).to match_array(expected_values)
101
+ end
102
+
103
+ it 'plucks correctly from ActiveRecord relation with associations' do
104
+ plucked = Wizard.all.joins(:wand).pluck_to_tstruct(TA[struct_type].new, associations: associations)
105
+ expect(plucked).to match_array(expected_values)
106
+ end
107
+ end
108
+
58
109
  context 'pluck 1 attribute' do
59
110
  it_should_behave_like 'pluck_to_tstruct', WizardName, [
60
111
  WizardName.new(name: "Harry Potter"),
@@ -72,8 +123,30 @@ RSpec.describe SorbetRails::PluckToTStruct do
72
123
  context 'given a wrong type' do
73
124
  it 'should raise error' do
74
125
  expect {
75
- plucked = Wizard.pluck_to_tstruct(TA[String].new)
126
+ Wizard.pluck_to_tstruct(TA[String].new)
76
127
  }.to raise_error(SorbetRails::PluckToTStruct::UnexpectedType)
77
128
  end
78
129
  end
130
+
131
+ context "given associations mappings that don't exist in T::Struct" do
132
+ it 'should raise error' do
133
+ expect {
134
+ Wizard.pluck_to_tstruct(
135
+ TA[WizardWithWandT].new,
136
+ associations: { wood_type: "wands.wood_type" }
137
+ )
138
+ }.to raise_error(SorbetRails::PluckToTStruct::UnexpectedAssociations)
139
+ end
140
+ end
141
+
142
+ context 'pluck with associations' do
143
+ associations = { wand_wood_type: "wands.wood_type" }
144
+
145
+ expected = [
146
+ WizardWithWandT.new(name: "Harry Potter", house: "Gryffindor", wand_wood_type: "Holly"),
147
+ WizardWithWandT.new(name: "Hermione Granger", house: "Gryffindor", wand_wood_type: "Vine"),
148
+ ]
149
+
150
+ it_should_behave_like 'pluck_to_tstruct with associations', WizardWithWandT, associations, expected
151
+ end
79
152
  end