sorbet-rails 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/lib/sorbet-rails/gem_plugins/active_flag_plugin.rb +0 -1
  4. data/lib/sorbet-rails/gem_plugins/paperclip_plugin.rb +0 -1
  5. data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +9 -2
  6. data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +0 -1
  7. data/lib/sorbet-rails/model_plugins/active_record_enum.rb +0 -1
  8. data/lib/sorbet-rails/model_plugins/active_record_named_scope.rb +15 -6
  9. data/lib/sorbet-rails/model_plugins/active_record_querying.rb +5 -1
  10. data/lib/sorbet-rails/model_plugins/base.rb +2 -1
  11. data/lib/sorbet-rails/model_rbi_formatter.rb +4 -8
  12. data/lib/sorbet-rails/model_utils.rb +68 -30
  13. data/lib/sorbet-rails/tasks/rails_rbi.rake +13 -21
  14. data/sorbet-rails.gemspec +1 -1
  15. data/spec/generators/rails-template.rb +3 -6
  16. data/spec/generators/sorbet_test_cases.rb +41 -26
  17. data/spec/support/v5.0/Gemfile.lock +2 -2
  18. data/spec/support/v5.0/app/models/spell_book.rb +2 -0
  19. data/spec/support/v5.0/app/models/wizard.rb +1 -1
  20. data/spec/support/v5.0/sorbet_test_cases.rb +41 -26
  21. data/spec/support/v5.1/Gemfile.lock +2 -2
  22. data/spec/support/v5.1/app/models/spell_book.rb +2 -0
  23. data/spec/support/v5.1/app/models/wizard.rb +1 -1
  24. data/spec/support/v5.1/sorbet_test_cases.rb +41 -26
  25. data/spec/support/v5.2/Gemfile.lock +2 -2
  26. data/spec/support/v5.2/app/models/spell_book.rb +2 -0
  27. data/spec/support/v5.2/app/models/wizard.rb +1 -1
  28. data/spec/support/v5.2/sorbet_test_cases.rb +41 -26
  29. data/spec/support/v6.0/Gemfile.lock +2 -2
  30. data/spec/support/v6.0/app/models/spell_book.rb +2 -0
  31. data/spec/support/v6.0/app/models/wizard.rb +1 -1
  32. data/spec/support/v6.0/sorbet_test_cases.rb +41 -26
  33. data/spec/test_data/v5.0/expected_headmaster.rbi +18 -190
  34. data/spec/test_data/v5.0/expected_internal_metadata.rbi +18 -188
  35. data/spec/test_data/v5.0/expected_potion.rbi +18 -188
  36. data/spec/test_data/v5.0/expected_robe.rbi +18 -190
  37. data/spec/test_data/v5.0/expected_schema_migration.rbi +18 -188
  38. data/spec/test_data/v5.0/expected_school.rbi +18 -190
  39. data/spec/test_data/v5.0/expected_spell.rbi +18 -190
  40. data/spec/test_data/v5.0/expected_spell/habtm_spell_books.rbi +18 -190
  41. data/spec/test_data/v5.0/expected_spell_book.rbi +55 -217
  42. data/spec/test_data/v5.0/expected_spell_book/habtm_spells.rbi +18 -190
  43. data/spec/test_data/v5.0/expected_squib.rbi +90 -264
  44. data/spec/test_data/v5.0/expected_subject.rbi +18 -190
  45. data/spec/test_data/v5.0/expected_subject/habtm_wizards.rbi +18 -190
  46. data/spec/test_data/v5.0/expected_wand.rbi +53 -227
  47. data/spec/test_data/v5.0/expected_wizard.rbi +90 -264
  48. data/spec/test_data/v5.0/expected_wizard/habtm_subjects.rbi +18 -190
  49. data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +90 -264
  50. data/spec/test_data/v5.1/expected_headmaster.rbi +18 -196
  51. data/spec/test_data/v5.1/expected_internal_metadata.rbi +18 -194
  52. data/spec/test_data/v5.1/expected_potion.rbi +18 -194
  53. data/spec/test_data/v5.1/expected_robe.rbi +18 -196
  54. data/spec/test_data/v5.1/expected_schema_migration.rbi +18 -194
  55. data/spec/test_data/v5.1/expected_school.rbi +18 -196
  56. data/spec/test_data/v5.1/expected_spell.rbi +18 -196
  57. data/spec/test_data/v5.1/expected_spell/habtm_spell_books.rbi +18 -196
  58. data/spec/test_data/v5.1/expected_spell_book.rbi +55 -223
  59. data/spec/test_data/v5.1/expected_spell_book/habtm_spells.rbi +18 -196
  60. data/spec/test_data/v5.1/expected_squib.rbi +91 -271
  61. data/spec/test_data/v5.1/expected_subject.rbi +18 -196
  62. data/spec/test_data/v5.1/expected_subject/habtm_wizards.rbi +18 -196
  63. data/spec/test_data/v5.1/expected_wand.rbi +53 -233
  64. data/spec/test_data/v5.1/expected_wizard.rbi +91 -271
  65. data/spec/test_data/v5.1/expected_wizard/habtm_subjects.rbi +18 -196
  66. data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +91 -271
  67. data/spec/test_data/v5.2/expected_attachment.rbi +18 -194
  68. data/spec/test_data/v5.2/expected_blob.rbi +39 -215
  69. data/spec/test_data/v5.2/expected_headmaster.rbi +18 -196
  70. data/spec/test_data/v5.2/expected_internal_metadata.rbi +18 -194
  71. data/spec/test_data/v5.2/expected_potion.rbi +18 -194
  72. data/spec/test_data/v5.2/expected_robe.rbi +18 -196
  73. data/spec/test_data/v5.2/expected_schema_migration.rbi +18 -194
  74. data/spec/test_data/v5.2/expected_school.rbi +18 -196
  75. data/spec/test_data/v5.2/expected_spell.rbi +18 -196
  76. data/spec/test_data/v5.2/expected_spell/habtm_spell_books.rbi +18 -196
  77. data/spec/test_data/v5.2/expected_spell_book.rbi +55 -223
  78. data/spec/test_data/v5.2/expected_spell_book/habtm_spells.rbi +18 -196
  79. data/spec/test_data/v5.2/expected_squib.rbi +95 -275
  80. data/spec/test_data/v5.2/expected_subject.rbi +18 -196
  81. data/spec/test_data/v5.2/expected_subject/habtm_wizards.rbi +18 -196
  82. data/spec/test_data/v5.2/expected_wand.rbi +53 -233
  83. data/spec/test_data/v5.2/expected_wizard.rbi +95 -275
  84. data/spec/test_data/v5.2/expected_wizard/habtm_subjects.rbi +18 -196
  85. data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +95 -275
  86. data/spec/test_data/v6.0/expected_attachment.rbi +18 -218
  87. data/spec/test_data/v6.0/expected_blob.rbi +31 -243
  88. data/spec/test_data/v6.0/expected_headmaster.rbi +18 -220
  89. data/spec/test_data/v6.0/expected_internal_metadata.rbi +18 -218
  90. data/spec/test_data/v6.0/expected_potion.rbi +18 -218
  91. data/spec/test_data/v6.0/expected_robe.rbi +18 -220
  92. data/spec/test_data/v6.0/expected_schema_migration.rbi +18 -218
  93. data/spec/test_data/v6.0/expected_school.rbi +18 -220
  94. data/spec/test_data/v6.0/expected_spell.rbi +18 -220
  95. data/spec/test_data/v6.0/expected_spell/habtm_spell_books.rbi +18 -220
  96. data/spec/test_data/v6.0/expected_spell_book.rbi +24 -258
  97. data/spec/test_data/v6.0/expected_spell_book/habtm_spells.rbi +18 -220
  98. data/spec/test_data/v6.0/expected_squib.rbi +31 -457
  99. data/spec/test_data/v6.0/expected_subject.rbi +18 -220
  100. data/spec/test_data/v6.0/expected_subject/habtm_wizards.rbi +18 -220
  101. data/spec/test_data/v6.0/expected_wand.rbi +18 -270
  102. data/spec/test_data/v6.0/expected_wizard.rbi +31 -457
  103. data/spec/test_data/v6.0/expected_wizard/habtm_subjects.rbi +18 -220
  104. data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +31 -457
  105. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ade7fadcb2cc5890f8cd5716247f14d7d9b0324029fb13de4f8b902e205a516
4
- data.tar.gz: 766dfdd66acea268fe696345ae871bf4b88cd67e2b845dffeb9f218d9a38586d
3
+ metadata.gz: 662c7c86f998ffbbdef82e035c7d827ef8f3f4192c4829e4e6ecfaaa0c72a131
4
+ data.tar.gz: 3eb66d37c008b8d8c3ae5b33fe8b645652dac7d2c047195e9773af743c2859c0
5
5
  SHA512:
6
- metadata.gz: 91ce6f173680ed297198333b0780c4f4257a7b95d56f33d6f464f67f122c37a6483b87f00392792219ce51724ad5379fea9b7cbf91fc100d439396f828f210be
7
- data.tar.gz: ba26ebc1b34724a1eee5cc667add0b843c3ef77994606938e31b2f3fc0a0a88d8a0b7ad39f65194350045166c1eb130cd94951f54c763d82a7efd19dd65ed7a0
6
+ metadata.gz: fd2212ba2d83e22a540540b71fd9c82e4ec67211c5577166bc99772b957ecb9d38fa2326dbeda9d2f03f399bdde73c542f148834cfe600d4f7b193f210427b74
7
+ data.tar.gz: c639bb6d3b6785f0b945354d4e034ab48515473d91267d20518a69fe829550611764ce0071d383474e0a2cb1f05db04092276d5b238c2b10a85c3e9113d8839a
data/README.md CHANGED
@@ -73,7 +73,7 @@ It is possible to add custom RBI generation logic for your custom module or gems
73
73
  We also add following methods to make type-checking more easily:
74
74
  - [`find_n`, `first_n`, `last_n`](https://github.com/chanzuckerberg/sorbet-rails#find-first-and-last)
75
75
  - [`pluck_to_tstruct`](#pluck_to_tstruct-instead-of-pluck)
76
- - [`typed_enum`](#enums)
76
+ - [`typed_enum`](#typed_enum-instead-of-enum)
77
77
  - [`Model::RelationType`](#relationtype-alias)
78
78
 
79
79
  #### `pluck_to_tstruct` instead of `pluck`
@@ -96,7 +96,7 @@ Wizard.all.pluck_to_tstruct(TA[WizardStruct].new) # T::Array[WizardStruct]
96
96
 
97
97
  This method is based on [pluck_to_hash](https://github.com/girishso/pluck_to_hash) gem.
98
98
 
99
- #### Enums
99
+ #### `typed_enum` instead of `enum`
100
100
 
101
101
  If you use [Rails `enum`](https://guides.rubyonrails.org/active_record_querying.html#enums), `sorbet-rails` will generate a corresponding `T::Enum`. It will also include getters, setters, and scope methods in the rbi file it generates.
102
102
 
@@ -203,7 +203,7 @@ end
203
203
  If it fails to coerce the params into the right type, an `ActionController::BadRequest` exception will be raised with the coercion context for debugging.
204
204
 
205
205
  Note: The API `TypedParams[...].new.extract!` may seem verbose, but necessary to support this feature. Ideally, the API can be simply `TypedParams.extract!(...)`. However, `sorbet` [doesn't support](http://github.com/sorbet/sorbet/issues/62) defining a method that accept a type and return an instance of the type. If this feature is supported by `sorbet` in the future, it will be easy to codemod to be `TypedParams.extract(...)!` part from your code.
206
- Note: [`require_typed` and `fetch_typed`](https://github.com/chanzuckerberg/sorbet-rails/blob/v0.5.9.1/README.md) are deprecated in favor of `TypedParams`.
206
+ Note: [`require_typed` and `fetch_typed`](https://github.com/chanzuckerberg/sorbet-rails/blob/v0.5.9.1/README.md) are deprecated in favor of `TypedParams`. They will be removed in v0.7.
207
207
 
208
208
  ### Routes
209
209
 
@@ -7,7 +7,6 @@ class ActiveFlagPlugin < SorbetRails::ModelPlugins::Base
7
7
 
8
8
  module_name = self.model_module_name("GeneratedActiveFlagMethods")
9
9
  module_rbi = root.create_module(module_name)
10
- module_rbi.create_extend("T::Sig")
11
10
 
12
11
  model_class_rbi = root.create_class(self.model_class_name)
13
12
  model_class_rbi.create_include(module_name)
@@ -8,7 +8,6 @@ class PaperclipPlugin < SorbetRails::ModelPlugins::Base
8
8
 
9
9
  module_name = self.model_module_name("GeneratedPaperclipMethods")
10
10
  module_rbi = root.create_module(module_name)
11
- module_rbi.create_extend("T::Sig")
12
11
 
13
12
  model_class_rbi = root.create_class(self.model_class_name)
14
13
  model_class_rbi.create_include(module_name)
@@ -13,7 +13,6 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
13
13
 
14
14
  assoc_module_name = self.model_module_name("GeneratedAssociationMethods")
15
15
  assoc_module_rbi = root.create_module(assoc_module_name)
16
- assoc_module_rbi.create_extend("T::Sig")
17
16
 
18
17
  model_class_rbi = root.create_class(self.model_class_name)
19
18
  model_class_rbi.create_include(assoc_module_name)
@@ -131,7 +130,15 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
131
130
 
132
131
  sig { params(reflection: T.untyped).returns(T.nilable(T::Boolean)) }
133
132
  def assoc_should_be_untyped?(reflection)
134
- polymorphic_assoc?(reflection) || !@available_classes.include?(reflection.klass.name)
133
+ # For some polymorphic associations (e.g. a has-many-through where the `source`
134
+ # is polymorphic) we can figure out the type from the class_name or source_type.
135
+ polymorpic_with_unknowable_klass = (
136
+ polymorphic_assoc?(reflection) &&
137
+ !reflection.options.key?(:class_name) &&
138
+ !reflection.options.key?(:source_type)
139
+ )
140
+
141
+ polymorpic_with_unknowable_klass || !@available_classes.include?(reflection.klass.name)
135
142
  end
136
143
 
137
144
  sig { params(reflection: T.untyped).returns(T.nilable(T::Boolean)) }
@@ -26,7 +26,6 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
26
26
 
27
27
  attribute_module_name = self.model_module_name("GeneratedAttributeMethods")
28
28
  attribute_module_rbi = root.create_module(attribute_module_name)
29
- attribute_module_rbi.create_extend("T::Sig")
30
29
 
31
30
  model_class_rbi = root.create_class(self.model_class_name)
32
31
  model_class_rbi.create_include(attribute_module_name)
@@ -9,7 +9,6 @@ class SorbetRails::ModelPlugins::ActiveRecordEnum < SorbetRails::ModelPlugins::B
9
9
 
10
10
  enum_module_name = model_module_name("EnumInstanceMethods")
11
11
  enum_module_rbi = root.create_module(enum_module_name)
12
- enum_module_rbi.create_extend("T::Sig")
13
12
 
14
13
  model_class_rbi = root.create_class(self.model_class_name)
15
14
  model_class_rbi.create_include(enum_module_name)
@@ -7,15 +7,24 @@ class SorbetRails::ModelPlugins::ActiveRecordNamedScope < SorbetRails::ModelPlug
7
7
  def generate(root)
8
8
  model_class_rbi = root.create_class(self.model_class_name)
9
9
 
10
- @model_class.methods.sort.each do |method_name|
10
+ # Named scope methods are dynamically defined by the `scope` method so their
11
+ # source_location is `lib/active_record/scoping/named.rb`. So we find scopes
12
+ # by two criteria:
13
+ # - they are defined in 'activerecord/lib/active_record/scoping/named.rb'
14
+ # - they are not one of the methods actually defined in that file's source.
15
+ # See: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/scoping/named.rb
16
+ non_dynamic_methods = (
17
+ ActiveRecord::Scoping::Named::ClassMethods.instance_methods +
18
+ ActiveRecord::Scoping::Named::ClassMethods.protected_instance_methods +
19
+ ActiveRecord::Scoping::Named::ClassMethods.private_instance_methods
20
+ )
21
+
22
+ (@model_class.methods - non_dynamic_methods).sort.each do |method_name|
11
23
  next unless SorbetRails::Utils.valid_method_name?(method_name.to_s)
24
+
12
25
  method_obj = @model_class.method(method_name)
13
26
  next unless method_obj.present? && method_obj.source_location.present?
14
- # we detect sscopes defined in a model by 2 criteria:
15
- # - they don't have an owner name
16
- # - they are defined in 'activerecord/lib/active_record/scoping/named.rb'
17
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/scoping/named.rb
18
- next unless method_obj.owner.name == nil
27
+
19
28
  source_file = method_obj.source_location[0]
20
29
  next unless source_file.include?("lib/active_record/scoping/named.rb")
21
30
 
@@ -9,6 +9,7 @@ class SorbetRails::ModelPlugins::ActiveRecordQuerying < SorbetRails::ModelPlugin
9
9
  add_relation_query_method(
10
10
  root,
11
11
  "all",
12
+ builtin_query_method: true,
12
13
  )
13
14
  add_relation_query_method(
14
15
  root,
@@ -16,6 +17,7 @@ class SorbetRails::ModelPlugins::ActiveRecordQuerying < SorbetRails::ModelPlugin
16
17
  parameters: [
17
18
  Parameter.new("&block", type: "T.nilable(T.proc.void)"),
18
19
  ],
20
+ builtin_query_method: true,
19
21
  )
20
22
 
21
23
  # It's not possible to typedef all methods in ActiveRecord::Querying module to have the
@@ -34,6 +36,7 @@ class SorbetRails::ModelPlugins::ActiveRecordQuerying < SorbetRails::ModelPlugin
34
36
  parameters: [
35
37
  Parameter.new("*args", type: "T.untyped"),
36
38
  ],
39
+ builtin_query_method: true,
37
40
  ) if exists_class_method?(method_name)
38
41
  end
39
42
 
@@ -43,7 +46,8 @@ class SorbetRails::ModelPlugins::ActiveRecordQuerying < SorbetRails::ModelPlugin
43
46
  parameters: [
44
47
  Parameter.new("*args", type: "T.untyped"),
45
48
  Parameter.new("&block", type: "T.nilable(T.proc.void)"),
46
- ]
49
+ ],
50
+ builtin_query_method: true,
47
51
  )
48
52
  end
49
53
  end
@@ -35,7 +35,8 @@ module SorbetRails::ModelPlugins
35
35
  model_class.validators_on(attribute).any? do |validator|
36
36
  validator.is_a?(ActiveModel::Validations::PresenceValidator) &&
37
37
  !validator.options.key?(:if) &&
38
- !validator.options.key?(:unless)
38
+ !validator.options.key?(:unless) &&
39
+ !validator.options.key?(:on)
39
40
  end
40
41
  end
41
42
  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
@@ -37,6 +37,16 @@ module SorbetRails::ModelUtils
37
37
  "#{model_class_name}::ActiveRecord_AssociationRelation"
38
38
  end
39
39
 
40
+ sig { returns(String) }
41
+ def model_query_methods_returning_relation_module_name
42
+ "#{model_class_name}::QueryMethodsReturningRelation"
43
+ end
44
+
45
+ sig { returns(String) }
46
+ def model_query_methods_returning_assoc_relation_module_name
47
+ "#{model_class_name}::QueryMethodsReturningAssociationRelation"
48
+ end
49
+
40
50
  sig { returns(String) }
41
51
  def model_relation_type_alias
42
52
  types = [
@@ -73,9 +83,13 @@ module SorbetRails::ModelUtils
73
83
  root: Parlour::RbiGenerator::Namespace,
74
84
  method_name: String,
75
85
  parameters: T.nilable(T::Array[::Parlour::RbiGenerator::Parameter]),
86
+ # This is meant to indicate the method is a rails-provided query method like
87
+ # where, limit, etc and not something like a named scope. It should likely
88
+ # only be set to `true` when called from the ActiveRecordQuerying plugin.
89
+ builtin_query_method: T::Boolean,
76
90
  ).void
77
91
  }
78
- def add_relation_query_method(root, method_name, parameters: nil)
92
+ def add_relation_query_method(root, method_name, parameters: nil, builtin_query_method: false)
79
93
  # a relation querying method will be available on
80
94
  # - model (as a class method)
81
95
  # - activerecord relation
@@ -84,34 +98,58 @@ module SorbetRails::ModelUtils
84
98
  # in case (1) and (2), it returns a Model::ActiveRecord_Relation
85
99
  # in case (3) and (4), it returns a Model::ActiveRecord_AssociationRelation
86
100
 
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
- )
101
+ # 'unscoped' is a special case where it always returns a ActiveRecord_Relation
102
+ assoc_return_value = method_name == 'unscoped' ? self.model_relation_class_name : self.model_assoc_relation_class_name
103
+
104
+ # We can put methods onto modules which are extended/included by the model
105
+ # and relation classes which reduces the RBI footprint for an individual
106
+ # model. However, in Rails 5 query methods that come from scopes or enums
107
+ # get overridden in hidden-definitions so we need to explicitly define them
108
+ # on the model and relation classes.
109
+ if builtin_query_method || Rails.version =~ /^6\./
110
+ relation_module_rbi = root.create_module(self.model_query_methods_returning_relation_module_name)
111
+ relation_module_rbi.create_method(
112
+ method_name,
113
+ parameters: parameters,
114
+ return_type: self.model_relation_class_name,
115
+ )
116
+
117
+ assoc_relation_module_rbi = root.create_module(self.model_query_methods_returning_assoc_relation_module_name)
118
+ assoc_relation_module_rbi.create_method(
119
+ method_name,
120
+ parameters: parameters,
121
+ return_type: assoc_return_value,
122
+ )
123
+ else
124
+ # force generating these methods because sorbet's hidden-definitions generate & override them
125
+ model_class_rbi = root.create_class(self.model_class_name)
126
+ model_class_rbi.create_method(
127
+ method_name,
128
+ parameters: parameters,
129
+ return_type: self.model_relation_class_name,
130
+ class_method: true,
131
+ )
132
+
133
+ model_relation_rbi = root.create_class(self.model_relation_class_name)
134
+ model_relation_rbi.create_method(
135
+ method_name,
136
+ parameters: parameters,
137
+ return_type: self.model_relation_class_name,
138
+ )
139
+
140
+ model_assoc_relation_rbi = root.create_class(self.model_assoc_relation_class_name)
141
+ model_assoc_relation_rbi.create_method(
142
+ method_name,
143
+ parameters: parameters,
144
+ return_type: assoc_return_value,
145
+ )
146
+
147
+ collection_proxy_rbi = root.create_class(self.model_assoc_proxy_class_name)
148
+ collection_proxy_rbi.create_method(
149
+ method_name,
150
+ parameters: parameters,
151
+ return_type: assoc_return_value,
152
+ )
153
+ end
116
154
  end
117
155
  end
@@ -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
 
@@ -145,22 +153,6 @@ namespace :rails_rbi do
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.6.4"
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)
@@ -90,6 +70,18 @@ T.assert_type!(Wizard.none?, T::Boolean)
90
70
  T.assert_type!(Wizard.one?, T::Boolean)
91
71
  # T.assert_type!(Wizard.update_all(name: 'Harry Potter'), Integer) # Ignored until we add support
92
72
  # T.assert_type!(Wizard.delete_all, Integer) # Ignored until we add support
73
+ # Query methods
74
+ T.assert_type!(Wizard.all, Wizard::ActiveRecord_Relation)
75
+ T.assert_type!(Wizard.recent, Wizard::ActiveRecord_Relation) # Named scope
76
+ T.assert_type!(Wizard.Gryffindor, Wizard::ActiveRecord_Relation) # Enum scope
77
+ T.assert_type!(Wizard.Gryffindor.recent, Wizard::ActiveRecord_Relation)
78
+ T.assert_type!(Wizard.Gryffindor.recent.unscoped, Wizard::ActiveRecord_Relation)
79
+ T.assert_type!(Wizard.where(id: 1), Wizard::ActiveRecord_Relation)
80
+ T.assert_type!(Wizard.where(id: 1).recent, Wizard::ActiveRecord_Relation)
81
+ T.assert_type!(Wizard.where.not(id: 1), Wizard::ActiveRecord_Relation)
82
+ T.assert_type!(Wizard.preload(:spell_books), Wizard::ActiveRecord_Relation)
83
+ T.assert_type!(Wizard.eager_load(:spell_books), Wizard::ActiveRecord_Relation)
84
+ T.assert_type!(Wizard.order(:id), Wizard::ActiveRecord_Relation)
93
85
 
94
86
  # Finder methods -- ActiveRecord::Relation
95
87
  T.assert_type!(Wizard.all.exists?(name: 'Harry Potter'), T::Boolean)
@@ -133,6 +125,18 @@ T.assert_type!(Wizard.all.none?, T::Boolean)
133
125
  T.assert_type!(Wizard.all.one?, T::Boolean)
134
126
  # T.assert_type!(Wizard.all.update_all(name: 'Harry Potter'), Integer) # Ignored until we add support
135
127
  # T.assert_type!(Wizard.all.delete_all, Integer) # Ignored until we add support
128
+ # Query methods
129
+ T.assert_type!(Wizard.all.all, Wizard::ActiveRecord_Relation)
130
+ T.assert_type!(Wizard.all.recent, Wizard::ActiveRecord_Relation) # Named scope
131
+ T.assert_type!(Wizard.all.Gryffindor, Wizard::ActiveRecord_Relation) # Enum scope
132
+ T.assert_type!(Wizard.all.Gryffindor.recent, Wizard::ActiveRecord_Relation)
133
+ T.assert_type!(Wizard.all.Gryffindor.recent.unscoped, Wizard::ActiveRecord_Relation)
134
+ T.assert_type!(Wizard.all.where(id: 1), Wizard::ActiveRecord_Relation)
135
+ T.assert_type!(Wizard.all.where(id: 1).recent, Wizard::ActiveRecord_Relation)
136
+ T.assert_type!(Wizard.all.where.not(id: 1), Wizard::ActiveRecord_Relation)
137
+ T.assert_type!(Wizard.all.preload(:spell_books), Wizard::ActiveRecord_Relation)
138
+ T.assert_type!(Wizard.all.eager_load(:spell_books), Wizard::ActiveRecord_Relation)
139
+ T.assert_type!(Wizard.all.order(:id), Wizard::ActiveRecord_Relation)
136
140
  # Enumerable methods
137
141
  Wizard.all.each { |w| T.assert_type!(w, Wizard) }
138
142
  Wizard.all.map { |w| T.assert_type!(w, Wizard) }
@@ -183,13 +187,18 @@ T.assert_type!(spell_books.none?, T::Boolean)
183
187
  T.assert_type!(spell_books.one?, T::Boolean)
184
188
  # T.assert_type!(spell_books.update_all(name: 'Fantastic Beasts'), Integer) # Ignored until we add support
185
189
  # T.assert_type!(spell_books.delete_all, Integer) # Ignored until we add support
186
- # CollectionProxy query also typed correctly!
190
+ # Query methods
191
+ T.assert_type!(spell_books.all, SpellBook::ActiveRecord_AssociationRelation)
192
+ T.assert_type!(spell_books.recent, SpellBook::ActiveRecord_AssociationRelation) # Named scope
193
+ T.assert_type!(spell_books.unclassified, SpellBook::ActiveRecord_AssociationRelation) # Enum scope
194
+ T.assert_type!(spell_books.unclassified.recent, SpellBook::ActiveRecord_AssociationRelation)
195
+ T.assert_type!(spell_books.unclassified.recent.unscoped, SpellBook::ActiveRecord_Relation) # Turns back into relation
187
196
  T.assert_type!(spell_books.where(id: 1), SpellBook::ActiveRecord_AssociationRelation)
197
+ T.assert_type!(spell_books.where(id: 1).recent, SpellBook::ActiveRecord_AssociationRelation)
198
+ T.assert_type!(spell_books.where.not(id: 1), SpellBook::ActiveRecord_AssociationRelation)
188
199
  T.assert_type!(spell_books.preload(:wizard), SpellBook::ActiveRecord_AssociationRelation)
189
200
  T.assert_type!(spell_books.eager_load(:wizard), SpellBook::ActiveRecord_AssociationRelation)
190
201
  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
202
  # Enumerable methods
194
203
  spell_books.each { |s| T.assert_type!(s, SpellBook) }
195
204
  spell_books.map { |s| T.assert_type!(s, SpellBook) }
@@ -248,12 +257,18 @@ T.assert_type!(spell_books_query.none?, T::Boolean)
248
257
  T.assert_type!(spell_books_query.one?, T::Boolean)
249
258
  # T.assert_type!(spell_books_query.update_all(name: 'Fantastic Beasts'), Integer) # Ignored until we add support
250
259
  # T.assert_type!(spell_books_query.delete_all, Integer) # Ignored until we add support
251
- # Query chaining
260
+ # Query methods
261
+ T.assert_type!(spell_books_query.all, SpellBook::ActiveRecord_AssociationRelation)
262
+ T.assert_type!(spell_books_query.recent, SpellBook::ActiveRecord_AssociationRelation) # Named scope
263
+ T.assert_type!(spell_books_query.unclassified, SpellBook::ActiveRecord_AssociationRelation) # Enum scope
264
+ T.assert_type!(spell_books_query.unclassified.recent, SpellBook::ActiveRecord_AssociationRelation)
265
+ T.assert_type!(spell_books_query.unclassified.recent.unscoped, SpellBook::ActiveRecord_Relation) # Turns back into relation
266
+ T.assert_type!(spell_books_query.where(id: 1), SpellBook::ActiveRecord_AssociationRelation)
267
+ T.assert_type!(spell_books_query.where(id: 1).recent, SpellBook::ActiveRecord_AssociationRelation)
268
+ T.assert_type!(spell_books_query.where.not(id: 1), SpellBook::ActiveRecord_AssociationRelation)
252
269
  T.assert_type!(spell_books_query.preload(:wizard), SpellBook::ActiveRecord_AssociationRelation)
253
270
  T.assert_type!(spell_books_query.eager_load(:wizard), SpellBook::ActiveRecord_AssociationRelation)
254
271
  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
272
  # Enumerable methods
258
273
  spell_books_query.each { |s| T.assert_type!(s, SpellBook) }
259
274
  spell_books_query.map { |s| T.assert_type!(s, SpellBook) }