sorbet-rails 0.5.8.1 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/README.md +27 -4
  4. data/lib/bundled_rbi/active_record_relation.rbi +79 -0
  5. data/lib/sorbet-rails/gem_plugins/active_flag_plugin.rb +47 -0
  6. data/lib/sorbet-rails/gem_plugins/kaminari_plugin.rb +20 -1
  7. data/lib/sorbet-rails/gem_plugins/paperclip_plugin.rb +33 -0
  8. data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +22 -6
  9. data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +10 -2
  10. data/lib/sorbet-rails/model_plugins/active_record_finder_methods.rb +46 -35
  11. data/lib/sorbet-rails/model_plugins/enumerable_collections.rb +40 -34
  12. data/lib/sorbet-rails/model_plugins/plugins.rb +6 -0
  13. data/lib/sorbet-rails/routes_rbi_formatter.rb +4 -0
  14. data/lib/sorbet-rails/tasks/rails_rbi.rake +2 -1
  15. data/sorbet-rails.gemspec +2 -2
  16. data/spec/generators/rails-template.rb +25 -3
  17. data/spec/generators/sorbet_test_cases.rb +5 -0
  18. data/spec/model_rbi_formatter_spec.rb +1 -1
  19. data/spec/rake_rails_rbi_models_spec.rb +1 -0
  20. data/spec/support/v5.0/Gemfile +1 -1
  21. data/spec/support/v5.0/Gemfile.lock +18 -14
  22. data/spec/support/v5.0/app/models/school.rb +3 -0
  23. data/spec/support/v5.0/app/models/spell_book.rb +3 -1
  24. data/spec/support/v5.0/app/models/wizard.rb +3 -0
  25. data/spec/support/v5.0/config/environments/development.rb +1 -1
  26. data/spec/support/v5.0/config/initializers/sorbet_rails.rb +1 -1
  27. data/spec/support/v5.0/db/migrate/20190620000003_create_spell_books.rb +1 -1
  28. data/spec/support/v5.0/db/migrate/20190620000008_add_robe_to_wizard.rb +1 -1
  29. data/spec/support/v5.0/db/migrate/20190620000009_add_school.rb +10 -0
  30. data/spec/support/v5.0/db/schema.rb +9 -4
  31. data/spec/support/v5.0/sorbet_test_cases.rb +5 -0
  32. data/spec/support/v5.1/Gemfile +1 -1
  33. data/spec/support/v5.1/Gemfile.lock +18 -14
  34. data/spec/support/v5.1/app/models/school.rb +3 -0
  35. data/spec/support/v5.1/app/models/spell_book.rb +3 -1
  36. data/spec/support/v5.1/app/models/wizard.rb +3 -0
  37. data/spec/support/v5.1/config/environments/test.rb +1 -1
  38. data/spec/support/v5.1/config/initializers/sorbet_rails.rb +1 -1
  39. data/spec/support/v5.1/db/migrate/20190620000003_create_spell_books.rb +1 -1
  40. data/spec/support/v5.1/db/migrate/20190620000008_add_robe_to_wizard.rb +1 -1
  41. data/spec/support/v5.1/db/migrate/20190620000009_add_school.rb +10 -0
  42. data/spec/support/v5.1/db/schema.rb +9 -4
  43. data/spec/support/v5.1/sorbet_test_cases.rb +5 -0
  44. data/spec/support/v5.2/Gemfile +1 -1
  45. data/spec/support/v5.2/Gemfile.lock +54 -50
  46. data/spec/support/v5.2/app/models/school.rb +3 -0
  47. data/spec/support/v5.2/app/models/spell_book.rb +3 -1
  48. data/spec/support/v5.2/app/models/wizard.rb +3 -0
  49. data/spec/support/v5.2/config/environments/development.rb +1 -1
  50. data/spec/support/v5.2/config/initializers/sorbet_rails.rb +1 -1
  51. data/spec/support/v5.2/db/migrate/20190620000003_create_spell_books.rb +1 -1
  52. data/spec/support/v5.2/db/migrate/20190620000008_add_robe_to_wizard.rb +1 -1
  53. data/spec/support/v5.2/db/migrate/20190620000009_add_school.rb +10 -0
  54. data/spec/support/v5.2/db/schema.rb +9 -4
  55. data/spec/support/v5.2/sorbet_test_cases.rb +5 -0
  56. data/spec/support/v6.0/Gemfile +1 -1
  57. data/spec/support/v6.0/Gemfile.lock +70 -66
  58. data/spec/support/v6.0/app/models/school.rb +3 -0
  59. data/spec/support/v6.0/app/models/spell_book.rb +3 -1
  60. data/spec/support/v6.0/app/models/wizard.rb +3 -0
  61. data/spec/support/v6.0/config/environments/development.rb +1 -1
  62. data/spec/support/v6.0/config/initializers/sorbet_rails.rb +1 -1
  63. data/spec/support/v6.0/db/migrate/20190620000003_create_spell_books.rb +1 -1
  64. data/spec/support/v6.0/db/migrate/20190620000008_add_robe_to_wizard.rb +1 -1
  65. data/spec/support/v6.0/db/migrate/20190620000009_add_school.rb +10 -0
  66. data/spec/support/v6.0/db/schema.rb +10 -4
  67. data/spec/support/v6.0/sorbet_test_cases.rb +5 -0
  68. data/spec/test_data/v5.0/expected_internal_metadata.rbi +0 -77
  69. data/spec/test_data/v5.0/expected_potion.rbi +0 -77
  70. data/spec/test_data/v5.0/expected_robe.rbi +23 -77
  71. data/spec/test_data/v5.0/expected_routes.rbi +4 -0
  72. data/spec/test_data/v5.0/expected_schema_migration.rbi +0 -77
  73. data/spec/test_data/v5.0/expected_school.rbi +651 -0
  74. data/spec/test_data/v5.0/expected_spell_book.rbi +3 -80
  75. data/spec/test_data/v5.0/expected_squib.rbi +17 -79
  76. data/spec/test_data/v5.0/expected_wand.rbi +4 -81
  77. data/spec/test_data/v5.0/expected_wizard.rbi +17 -79
  78. data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +17 -79
  79. data/spec/test_data/v5.1/expected_internal_metadata.rbi +0 -77
  80. data/spec/test_data/v5.1/expected_potion.rbi +0 -77
  81. data/spec/test_data/v5.1/expected_robe.rbi +23 -77
  82. data/spec/test_data/v5.1/expected_routes.rbi +4 -0
  83. data/spec/test_data/v5.1/expected_schema_migration.rbi +0 -77
  84. data/spec/test_data/v5.1/expected_school.rbi +663 -0
  85. data/spec/test_data/v5.1/expected_spell_book.rbi +3 -80
  86. data/spec/test_data/v5.1/expected_squib.rbi +17 -79
  87. data/spec/test_data/v5.1/expected_wand.rbi +4 -81
  88. data/spec/test_data/v5.1/expected_wizard.rbi +17 -79
  89. data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +17 -79
  90. data/spec/test_data/v5.2/expected_attachment.rbi +0 -77
  91. data/spec/test_data/v5.2/expected_blob.rbi +0 -77
  92. data/spec/test_data/v5.2/expected_internal_metadata.rbi +0 -77
  93. data/spec/test_data/v5.2/expected_potion.rbi +0 -77
  94. data/spec/test_data/v5.2/expected_robe.rbi +23 -77
  95. data/spec/test_data/v5.2/expected_routes.rbi +4 -0
  96. data/spec/test_data/v5.2/expected_schema_migration.rbi +0 -77
  97. data/spec/test_data/v5.2/expected_school.rbi +663 -0
  98. data/spec/test_data/v5.2/expected_spell_book.rbi +3 -80
  99. data/spec/test_data/v5.2/expected_squib.rbi +17 -79
  100. data/spec/test_data/v5.2/expected_wand.rbi +4 -81
  101. data/spec/test_data/v5.2/expected_wizard.rbi +17 -79
  102. data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +17 -79
  103. data/spec/test_data/v6.0/expected_attachment.rbi +0 -77
  104. data/spec/test_data/v6.0/expected_blob.rbi +0 -77
  105. data/spec/test_data/v6.0/expected_internal_metadata.rbi +0 -77
  106. data/spec/test_data/v6.0/expected_potion.rbi +0 -77
  107. data/spec/test_data/v6.0/expected_robe.rbi +23 -77
  108. data/spec/test_data/v6.0/expected_routes.rbi +4 -0
  109. data/spec/test_data/v6.0/expected_schema_migration.rbi +0 -77
  110. data/spec/test_data/v6.0/expected_school.rbi +711 -0
  111. data/spec/test_data/v6.0/expected_spell_book.rbi +3 -80
  112. data/spec/test_data/v6.0/expected_squib.rbi +17 -79
  113. data/spec/test_data/v6.0/expected_wand.rbi +4 -81
  114. data/spec/test_data/v6.0/expected_wizard.rbi +17 -79
  115. data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +17 -79
  116. metadata +31 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d15a660642f3208ed9262e89d7135ef6491c5744febd95838a2f1e3e49da2d5
4
- data.tar.gz: a276904cf439043622ff87e05a9048600afa497c768e7237b7499f84ed7b47ee
3
+ metadata.gz: bebc32701fb31995a1cf7f25ae7c2c7a9088347e8665dad3f182474483f03cc8
4
+ data.tar.gz: 8ae391dbadcc26831eebfef0969834969fe7c77ca8dd203f9ef05a7be26c19e0
5
5
  SHA512:
6
- metadata.gz: 50407eaa9170838b6060a94d1a31b5558cd4b6b70e90a508dd5bd366873e063a91d8cb2e32c11cf5ccc1410f9756d15f1fdecf5596e19a0a9e2080bf0deac763
7
- data.tar.gz: 87acca40218683be192a32006e6339d0829d821d28225c9f7311cbd2ae88199e13748461c4cf13d2698dec86020cb8fac6fe4fe08b66ebb0a0852e7e10744e5d
6
+ metadata.gz: 79759ed27498ebdced14582be0acfd6eef70c088a0eacf31335c9043cd43c22f5dc3b7f7638ea2c5ca1ba73e7209bc5cea3780abcae7ba427572050ee7414c39
7
+ data.tar.gz: a99f4c32fd69d120cbad6efc954c11fa8ad9cfbc6adb4c7e6e72422129984e25904134d725dfc8aec96b47dab9193085352a4fd55858124a5906ae41c8b42498
data/.travis.yml CHANGED
@@ -16,7 +16,7 @@ rvm:
16
16
  matrix:
17
17
  include:
18
18
  - rvm: 2.5
19
- env: RAILS_VERSION=5.2 SORBET_VERSION=0.4.4755
19
+ env: RAILS_VERSION=5.2 SORBET_VERSION=0.4.4929
20
20
  exclude:
21
21
  - rvm: 2.3
22
22
  env: RAILS_VERSION=6.0
data/README.md CHANGED
@@ -19,16 +19,15 @@ This gem adds a few Rake tasks to generate Ruby Interface (RBI) files for dynami
19
19
 
20
20
  ```
21
21
  # -- Gemfile --
22
-
23
22
  gem 'sorbet-rails'
24
23
  ```
25
24
 
25
+ Add `sorbet-rails` to the [`:default` group](https://bundler.io/v2.0/guides/groups.html) because of "Features Provided at Runtime" below.
26
+
26
27
  ```sh
27
28
  ❯ bundle install
28
29
  ```
29
30
 
30
- Warning: *don't* add `sorbet-rails` to a specific environment group (eg. `development` only). `sorbet-rails` adds a bunch of helper methods to your Rails runtime as well as generators for RBI files. You'll want to run the gem in all environments.
31
-
32
31
  3. Generate RBI files for your routes, models, etc
33
32
  ```sh
34
33
  ❯ rake rails_rbi:routes
@@ -48,7 +47,7 @@ Warning: *don't* add `sorbet-rails` to a specific environment group (eg. `develo
48
47
  ```
49
48
  Because we've generated RBI files for routes, models, and helpers, a lot more files should be typecheckable now. Many methods in `hidden.rbi` may be removed because they are now typed.
50
49
 
51
- ## Type-checking Rails code
50
+ ## Static RBI Generation
52
51
 
53
52
  ### Models
54
53
 
@@ -148,6 +147,26 @@ Since mailing action methods is based on instance methods defined in a mailer cl
148
147
  - If not, all the params in the mailing action method will be T.untyped.
149
148
  - For return type though, the mailing action method will return `ActionMailer::MessageDelivery` instead of the return type of the instance method.
150
149
 
150
+ ## Runtime Features
151
+
152
+ In addition to features provided by the static generator, `sorbet-rails` can
153
+ provide additional features when `require`d. This is why the installation
154
+ instructions specify that `sorbet-rails` should be placed in the [`:default`
155
+ group](https://bundler.io/v2.0/guides/groups.html) of the `Gemfile`, not a
156
+ specific environment group (eg. `development` only).
157
+
158
+ - Relation class: Making the relations available at runtime (they are normally private constants, the gem makes them public)
159
+ - Examples: `User::ActiveRecord_Relation`, `User::ActiveRecord_AssociationRelation`
160
+ - Model: `find_n`, `first_n`, `last_n` are the methods we added. `pluck_to_tstruct` as well.
161
+ - Controller: adding `fetch_typed` and `require_typed`
162
+
163
+ In addition to `require`ing `sorbet-rails`, you must also run
164
+ `rake rails_rbi:custom`, which will produce the RBI for these runtime features.
165
+
166
+ Discussion:
167
+ [#211](https://github.com/chanzuckerberg/sorbet-rails/issues/211),
168
+ [#214](https://github.com/chanzuckerberg/sorbet-rails/pull/214#issuecomment-546505485)
169
+
151
170
  ## Tips & Tricks
152
171
 
153
172
  ### Overriding generated signatures
@@ -342,6 +361,8 @@ These are the currently-supported gems and their symbolized names:
342
361
  | [Kaminari] | `:kaminari` |
343
362
  | [PgSearch] | `:pg_search` |
344
363
  | [Shrine] | `:shrine` |
364
+ | [active_flag]| `:active_flag` |
365
+ | [Paperclip] | `:paperclip` |
345
366
 
346
367
  You can also configure the core model plugins if needed. The default plugins are defined in the [config](https://github.com/chanzuckerberg/sorbet-rails/blob/master/lib/sorbet-rails/config.rb). For the full list of plugin symbols, check out [here](https://github.com/chanzuckerberg/sorbet-rails/blob/master/lib/sorbet-rails/model_plugins/plugins.rb).
347
368
 
@@ -351,6 +372,8 @@ You can also configure the core model plugins if needed. The default plugins are
351
372
  [FriendlyId]: https://github.com/norman/friendly_id
352
373
  [ElasticSearch]: https://github.com/elastic/elasticsearch-rails
353
374
  [Shrine]: https://github.com/shrinerb/shrine
375
+ [active_flag]: https://github.com/kenn/active_flag
376
+ [Paperclip]: https://github.com/thoughtbot/paperclip
354
377
 
355
378
  ## Contributing
356
379
 
@@ -0,0 +1,79 @@
1
+ # typed: strong
2
+ class ActiveRecord::Relation
3
+ sig { params(args: T.untyped).returns(Elem) }
4
+ def find(*args); end
5
+
6
+ sig { params(args: T.untyped).returns(T.nilable(Elem)) }
7
+ def find_by(*args); end
8
+
9
+ sig { params(args: T.untyped).returns(Elem) }
10
+ def find_by!(*args); end
11
+
12
+ sig { returns(T.nilable(Elem)) }
13
+ def first; end
14
+
15
+ sig { returns(Elem) }
16
+ def first!; end
17
+
18
+ sig { returns(T.nilable(Elem)) }
19
+ def second; end
20
+
21
+ sig { returns(Elem) }
22
+ def second!; end
23
+
24
+ sig { returns(T.nilable(Elem)) }
25
+ def third; end
26
+
27
+ sig { returns(Elem) }
28
+ def third!; end
29
+
30
+ sig { returns(T.nilable(Elem)) }
31
+ def third_to_last; end
32
+
33
+ sig { returns(Elem) }
34
+ def third_to_last!; end
35
+
36
+ sig { returns(T.nilable(Elem)) }
37
+ def second_to_last; end
38
+
39
+ sig { returns(Elem) }
40
+ def second_to_last!; end
41
+
42
+ sig { returns(T.nilable(Elem)) }
43
+ def last; end
44
+
45
+ sig { returns(Elem) }
46
+ def last!; end
47
+
48
+ sig { override.params(block: T.proc.params(e: Elem).void).returns(T::Array[Elem]) }
49
+ def each(&block); end
50
+
51
+ sig { params(level: T.nilable(Integer)).returns(T::Array[Elem]) }
52
+ def flatten(level); end
53
+
54
+ sig { returns(T::Array[Elem]) }
55
+ def to_a; end
56
+
57
+ sig do
58
+ type_parameters(:U).params(
59
+ blk: T.proc.params(arg0: Elem).returns(T.type_parameter(:U)),
60
+ )
61
+ .returns(T::Array[T.type_parameter(:U)])
62
+ end
63
+ def map(&blk); end
64
+
65
+ sig { params(conditions: T.untyped).returns(T::Boolean) }
66
+ def exists?(conditions = nil); end
67
+
68
+ sig { returns(T::Boolean) }
69
+ def any?; end
70
+
71
+ sig { returns(T::Boolean) }
72
+ def many?; end
73
+
74
+ sig { returns(T::Boolean) }
75
+ def none?; end
76
+
77
+ sig { returns(T::Boolean) }
78
+ def one?; end
79
+ end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ class ActiveFlagPlugin < SorbetRails::ModelPlugins::Base
3
+ sig { params(root: Parlour::RbiGenerator::Namespace).void.override }
4
+ def generate(root)
5
+ # method added here: https://github.com/kenn/active_flag/blob/master/lib/active_flag.rb#L13
6
+ return unless model_class.respond_to?(:active_flags)
7
+
8
+ module_name = self.model_module_name("GeneratedActiveFlagMethods")
9
+ module_rbi = root.create_module(module_name)
10
+ module_rbi.create_extend("T::Sig")
11
+
12
+ model_class_rbi = root.create_class(self.model_class_name)
13
+ model_class_rbi.create_include(module_name)
14
+
15
+ module_rbi.create_method(
16
+ "active_flags",
17
+ return_type: "T::Hash[Symbol, ActiveFlag::Definition]",
18
+ class_method: true
19
+ )
20
+
21
+ active_flag_keys(model_class).each do |flag|
22
+ module_rbi.create_method(
23
+ flag.to_s,
24
+ return_type: "::ActiveFlag::Value"
25
+ )
26
+
27
+ module_rbi.create_method(
28
+ "#{flag}=",
29
+ parameters: [
30
+ Parameter.new("value", type: "T::Array[Symbol]")
31
+ ],
32
+ return_type: nil
33
+ )
34
+
35
+ module_rbi.create_method(
36
+ flag.to_s.pluralize,
37
+ return_type: "::ActiveFlag::Definition",
38
+ class_method: true
39
+ )
40
+ end
41
+ end
42
+
43
+ sig { params(model_class: T.class_of(ActiveRecord::Base)).returns(T::Array[Symbol]) }
44
+ def active_flag_keys(model_class)
45
+ T.unsafe(model_class).active_flags.keys
46
+ end
47
+ end
@@ -1,7 +1,6 @@
1
1
  # typed: strict
2
2
  class KaminariPlugin < SorbetRails::ModelPlugins::Base
3
3
  # Kaminari generates a dynamic `page` method on ActiveRecord relations.
4
- # https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-activerecord/lib/kaminari/activerecord/active_record_model_extension.rb#L15
5
4
  sig { override.params(root: Parlour::RbiGenerator::Namespace).void }
6
5
  def generate(root)
7
6
  return unless @model_class.include?(::Kaminari::ActiveRecordModelExtension)
@@ -9,6 +8,7 @@ class KaminariPlugin < SorbetRails::ModelPlugins::Base
9
8
  # Get the configured Kaminari page method name, or fall back to 'page' if necessary.
10
9
  page_method = T.unsafe(::Kaminari).config.page_method_name || 'page'
11
10
 
11
+ # https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-activerecord/lib/kaminari/activerecord/active_record_model_extension.rb#L15
12
12
  add_relation_query_method(
13
13
  root,
14
14
  page_method.to_s,
@@ -16,5 +16,24 @@ class KaminariPlugin < SorbetRails::ModelPlugins::Base
16
16
  Parameter.new('num', type: 'T.nilable(Integer)', default: 'nil')
17
17
  ],
18
18
  )
19
+
20
+ # https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-core/lib/kaminari/models/page_scope_methods.rb#L7
21
+ add_relation_query_method(
22
+ root,
23
+ "per",
24
+ parameters: [
25
+ Parameter.new('num', type: 'Integer'),
26
+ Parameter.new('max_per_page', type: 'T.nilable(Integer)', default: 'nil')
27
+ ],
28
+ )
29
+
30
+ # https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-core/lib/kaminari/models/page_scope_methods.rb#L27
31
+ add_relation_query_method(
32
+ root,
33
+ "padding",
34
+ parameters: [
35
+ Parameter.new('num', type: 'Integer')
36
+ ],
37
+ )
19
38
  end
20
39
  end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+ class PaperclipPlugin < SorbetRails::ModelPlugins::Base
3
+ sig { params(root: Parlour::RbiGenerator::Namespace).void.override }
4
+ def generate(root)
5
+ # method added here: https://github.com/thoughtbot/paperclip/blob/v5.2.1/lib/paperclip/has_attached_file.rb#L110
6
+
7
+ return unless model_class.respond_to?(:attachment_definitions)
8
+
9
+ module_name = self.model_module_name("GeneratedPaperclipMethods")
10
+ module_rbi = root.create_module(module_name)
11
+ module_rbi.create_extend("T::Sig")
12
+
13
+ model_class_rbi = root.create_class(self.model_class_name)
14
+ model_class_rbi.create_include(module_name)
15
+
16
+ T.unsafe(::Paperclip::AttachmentRegistry).names_for(model_class).each do |attachment|
17
+ # https://github.com/thoughtbot/paperclip/blob/v5.2.1/lib/paperclip/has_attached_file.rb#L42
18
+ module_rbi.create_method(
19
+ attachment.to_s,
20
+ return_type: "::Paperclip::Attachment"
21
+ )
22
+
23
+ # https://github.com/thoughtbot/paperclip/blob/v5.2.1/lib/paperclip/attachment.rb#L100
24
+ module_rbi.create_method(
25
+ "#{attachment}=",
26
+ parameters: [
27
+ Parameter.new("uploaded_file", type: "T.untyped") # could be a variety of things
28
+ ],
29
+ return_type: nil
30
+ )
31
+ end
32
+ end
33
+ end
@@ -56,13 +56,29 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
56
56
  # optional (via `optional` or `!required` or `!belongs_to_required_by_default`)
57
57
  return false if !reflection.belongs_to?
58
58
 
59
- if reflection.options.key?(:required)
60
- !!reflection.options[:required]
61
- elsif reflection.options.key?(:optional)
62
- !reflection.options[:optional]
63
- else
64
- !!reflection.active_record.belongs_to_required_by_default
59
+ rails_required_config =
60
+ if reflection.options.key?(:required)
61
+ !!reflection.options[:required]
62
+ elsif reflection.options.key?(:optional)
63
+ !reflection.options[:optional]
64
+ else
65
+ !!reflection.active_record.belongs_to_required_by_default
66
+ end
67
+
68
+ column_def = @columns_hash[reflection.foreign_key.to_s]
69
+ db_required_config = column_def && !column_def.null
70
+
71
+ if rails_required_config && !db_required_config
72
+ puts "Warning: belongs_to association #{reflection.name} is required at the application
73
+ level but **nullable** at the DB level. Add a constraint at the DB level
74
+ (using `null: false` and foreign key constraint) to ensure it is enforced.".squish!
75
+ elsif !rails_required_config && db_required_config
76
+ puts "Warning: belongs_to association #{reflection.name} is specified as not-null at the
77
+ DB level but **not required** at the application level. Add a constraint at the app level
78
+ (using `optional: false`) as a validation hint to Rails.".squish!
65
79
  end
80
+
81
+ rails_required_config || db_required_config
66
82
  end
67
83
 
68
84
  sig do
@@ -131,15 +131,23 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
131
131
 
132
132
  sig { params(column_type: Object).returns(String) }
133
133
  def value_type_for_attr_writer(column_type)
134
- assignable_time_types = [DateTime, Date, Time, ActiveSupport::TimeWithZone].map(&:to_s)
135
-
136
134
  # it's safe - and convenient - to assign any "time like" object to a time zone
137
135
  # aware attribute because Rails will cast it to a `ActiveSupport::TimeWithZone`
138
136
  # (so rereading the attribute will always return the `TimeWithZone` type)
137
+ assignable_time_types = [DateTime, Date, Time, ActiveSupport::TimeWithZone].map(&:to_s)
138
+
139
+ # same thing applies with the many types that respond to `#to_i` or `#to_f`
140
+ assignable_numeric_types = [Integer, Float, ActiveSupport::Duration]
141
+
142
+ # TODO: this could be a lot tidier
139
143
  if column_type == ActiveSupport::TimeWithZone
140
144
  "T.any(#{assignable_time_types.join(', ')})"
141
145
  elsif column_type == "T.nilable(ActiveSupport::TimeWithZone)"
142
146
  "T.nilable(T.any(#{assignable_time_types.join(', ')}))"
147
+ elsif ["T.nilable(Float)", "T.nilable(Integer)"].include?(column_type)
148
+ "T.nilable(T.any(#{assignable_numeric_types.join(', ')}))"
149
+ elsif ["Float", "Integer"].include?(column_type.to_s)
150
+ "T.any(#{assignable_numeric_types.join(', ')})"
143
151
  elsif column_type == String
144
152
  'T.any(String, Symbol)'
145
153
  else
@@ -8,7 +8,7 @@ class SorbetRails::ModelPlugins::ActiveRecordFinderMethods < SorbetRails::ModelP
8
8
  create_finder_methods_for(model_class_rbi, class_method: true)
9
9
 
10
10
  model_relation_class_rbi = root.create_class(self.model_relation_class_name)
11
- create_finder_methods_for(model_relation_class_rbi, class_method: false)
11
+ create_finder_methods_for(model_relation_class_rbi, class_method: false, include_methods_that_return_self: false)
12
12
 
13
13
  model_assoc_proxy_class_rbi = root.create_class(self.model_assoc_proxy_class_name)
14
14
  create_finder_methods_for(model_assoc_proxy_class_rbi, class_method: false)
@@ -17,46 +17,57 @@ class SorbetRails::ModelPlugins::ActiveRecordFinderMethods < SorbetRails::ModelP
17
17
  create_finder_methods_for(model_assoc_relation_rbi, class_method: false)
18
18
  end
19
19
 
20
- sig { params(class_rbi: Parlour::RbiGenerator::ClassNamespace, class_method: T::Boolean).void }
21
- def create_finder_methods_for(class_rbi, class_method:)
22
- class_rbi.create_method(
23
- "find",
24
- parameters: [ Parameter.new("*args", type: "T.untyped") ],
25
- return_type: self.model_class_name,
26
- class_method: class_method,
27
- )
28
- class_rbi.create_method(
29
- "find_by",
30
- parameters: [ Parameter.new("*args", type: "T.untyped") ],
31
- return_type: "T.nilable(#{self.model_class_name})",
32
- class_method: class_method,
33
- )
34
- class_rbi.create_method(
35
- "find_by!",
36
- parameters: [ Parameter.new("*args", type: "T.untyped") ],
37
- return_type: self.model_class_name,
38
- class_method: class_method
39
- )
20
+ sig do
21
+ params(
22
+ class_rbi: Parlour::RbiGenerator::ClassNamespace,
23
+ class_method: T::Boolean,
24
+ include_methods_that_return_self: T::Boolean
25
+ ).void
26
+ end
27
+ def create_finder_methods_for(class_rbi, class_method:, include_methods_that_return_self: true)
28
+ # The intent of this is to prevent these methods from being generated
29
+ # unnecessarily when T.attached_class or Elem can be used to reduce
30
+ # the number of method signatures that have to be generated.
31
+ if include_methods_that_return_self
32
+ class_rbi.create_method(
33
+ "find",
34
+ parameters: [ Parameter.new("*args", type: "T.untyped") ],
35
+ return_type: self.model_class_name,
36
+ class_method: class_method,
37
+ )
38
+ class_rbi.create_method(
39
+ "find_by",
40
+ parameters: [ Parameter.new("*args", type: "T.untyped") ],
41
+ return_type: "T.nilable(#{self.model_class_name})",
42
+ class_method: class_method,
43
+ )
44
+ class_rbi.create_method(
45
+ "find_by!",
46
+ parameters: [ Parameter.new("*args", type: "T.untyped") ],
47
+ return_type: self.model_class_name,
48
+ class_method: class_method
49
+ )
40
50
 
41
- ["first", "second", "third", "third_to_last", "second_to_last", "last"].
42
- each do |method_name|
43
- create_finder_method_pair(class_rbi, method_name, class_method)
44
- end
51
+ ["first", "second", "third", "third_to_last", "second_to_last", "last"].
52
+ each do |method_name|
53
+ create_finder_method_pair(class_rbi, method_name, class_method)
54
+ end
45
55
 
46
- # Checker methods
47
- class_rbi.create_method(
48
- "exists?",
49
- parameters: [ Parameter.new("conditions", type: "T.untyped", default: "nil") ],
50
- return_type: "T::Boolean",
51
- class_method: class_method,
52
- )
53
- ["any?", "many?", "none?", "one?"].each do |method_name|
56
+ # Checker methods
54
57
  class_rbi.create_method(
55
- method_name,
56
- parameters: [ Parameter.new("*args", type: "T.untyped") ],
58
+ "exists?",
59
+ parameters: [ Parameter.new("conditions", type: "T.untyped", default: "nil") ],
57
60
  return_type: "T::Boolean",
58
61
  class_method: class_method,
59
62
  )
63
+ ["any?", "many?", "none?", "one?"].each do |method_name|
64
+ class_rbi.create_method(
65
+ method_name,
66
+ parameters: [ Parameter.new("*args", type: "T.untyped") ],
67
+ return_type: "T::Boolean",
68
+ class_method: class_method,
69
+ )
70
+ end
60
71
  end
61
72
  end
62
73
 
@@ -4,10 +4,10 @@ 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 & association proxy are enumerable
7
+ # model relation, assocation relation, & association proxy are enumerable
8
8
  # we need to implement "each" in these methods so that they work
9
9
  model_relation_class_rbi = root.create_class(self.model_relation_class_name)
10
- create_enumerable_methods_for(model_relation_class_rbi)
10
+ create_enumerable_methods_for(model_relation_class_rbi, include_methods: false)
11
11
 
12
12
  model_assoc_relation_rbi = root.create_class(self.model_assoc_relation_class_name)
13
13
  create_enumerable_methods_for(model_assoc_relation_rbi)
@@ -28,38 +28,44 @@ class SorbetRails::ModelPlugins::EnumerableCollections < SorbetRails::ModelPlugi
28
28
  end
29
29
  end
30
30
 
31
- sig { params(class_rbi: Parlour::RbiGenerator::ClassNamespace).void }
32
- def create_enumerable_methods_for(class_rbi)
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
33
  class_rbi.create_include("Enumerable")
34
- class_rbi.create_method(
35
- "each",
36
- parameters: [
37
- Parameter.new("&block", type: "T.proc.params(e: #{self.model_class_name}).void")
38
- ],
39
- return_type: "T::Array[#{self.model_class_name}]",
40
- implementation: true,
41
- )
42
- class_rbi.create_method(
43
- "flatten",
44
- parameters: [ Parameter.new("level", type: "T.nilable(Integer)") ],
45
- return_type: "T::Array[#{self.model_class_name}]",
46
- )
47
- # this is an escape hatch when there are conflicts in signatures of Enumerable & ActiveRecord
48
- class_rbi.create_method(
49
- "to_a",
50
- return_type: "T::Array[#{self.model_class_name}]",
51
- )
52
- # TODO use type_parameters(:U) when parlour supports it
53
- class_rbi.create_arbitrary(
54
- code: <<~RUBY
55
- sig do
56
- type_parameters(:U).params(
57
- blk: T.proc.params(arg0: Elem).returns(T.type_parameter(:U)),
58
- )
59
- .returns(T::Array[T.type_parameter(:U)])
60
- end
61
- def map(&blk); end
62
- RUBY
63
- )
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
64
70
  end
65
71
  end
@@ -78,6 +78,12 @@ module SorbetRails::ModelPlugins
78
78
  when :elastic_search
79
79
  require('sorbet-rails/gem_plugins/elastic_search_plugin')
80
80
  ElasticSearchPlugin
81
+ when :active_flag
82
+ require('sorbet-rails/gem_plugins/active_flag_plugin')
83
+ ActiveFlagPlugin
84
+ when :paperclip
85
+ require('sorbet-rails/gem_plugins/paperclip_plugin')
86
+ PaperclipPlugin
81
87
  else
82
88
  raise UnrecognizedPluginName.new(
83
89
  "Unrecognized plugin with name: #{plugin_name}. Please check available plugins in the
@@ -31,6 +31,10 @@ class SorbetRails::RoutesRbiFormatter
31
31
  @parlour.root.create_module('ActionView::Helpers') do |mod|
32
32
  mod.create_include('GeneratedUrlHelpers')
33
33
  end
34
+
35
+ @parlour.root.create_class('ActionMailer::Base') do |mod|
36
+ mod.create_include('GeneratedUrlHelpers')
37
+ end
34
38
  end
35
39
 
36
40
  sig { params(routes: T.untyped, filter: T.untyped).void }
@@ -37,6 +37,7 @@ namespace :rails_rbi do
37
37
  copy_bundled_rbi('type_assert.rbi')
38
38
  copy_bundled_rbi('parameters.rbi')
39
39
  copy_bundled_rbi('pluck_to_tstruct.rbi')
40
+ copy_bundled_rbi('active_record_relation.rbi')
40
41
  end
41
42
 
42
43
  desc "Generate rbis for rails models. Pass models name to regenerate rbi for only the given models."
@@ -113,7 +114,7 @@ namespace :rails_rbi do
113
114
  begin
114
115
  formatter = SorbetRails::ModelRbiFormatter.new(model_class, available_class_names)
115
116
  [model_class.name, formatter.generate_rbi]
116
- rescue StandardError => ex
117
+ rescue StandardError, NotImplementedError => ex
117
118
  puts "---"
118
119
  puts "Error when handling model #{model_class.name}: #{ex}"
119
120
  nil
data/sorbet-rails.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{sorbet-rails}
3
- s.version = "0.5.8.1"
3
+ s.version = "0.5.9"
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"]
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  `git ls-files -z`.split("\x0").select { |f| f.match(%r{^(test|spec|features)/}) }
16
16
  end
17
17
 
18
- s.add_dependency 'parlour', '~> 0.8.0'
18
+ s.add_dependency 'parlour', '~> 1.0'
19
19
 
20
20
  # Development
21
21
  s.add_development_dependency 'rspec', '~> 3.8', '>= 3.8'