sorbet-rails 0.5.5 → 0.5.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1
  3. data/lib/sorbet-rails.rb +2 -0
  4. data/lib/sorbet-rails/custom_types/boolean_string.rb +32 -0
  5. data/lib/sorbet-rails/custom_types/integer_string.rb +35 -0
  6. data/lib/sorbet-rails/model_plugins/active_record_enum.rb +5 -1
  7. data/lib/sorbet-rails/rails_mixins/active_record_overrides.rb +18 -4
  8. data/lib/sorbet-rails/tasks/rails_rbi.rake +1 -2
  9. data/lib/sorbet-rails/type_assert/type_assert.rb +0 -1
  10. data/lib/sorbet-rails/type_assert/type_assert_impl.rb +16 -16
  11. data/sorbet-rails.gemspec +1 -1
  12. data/spec/boolean_string_spec.rb +59 -0
  13. data/spec/generators/rails-template.rb +16 -0
  14. data/spec/integer_string_spec.rb +46 -0
  15. data/spec/rake_rails_rbi_models_spec.rb +1 -0
  16. data/spec/support/v4.2/Gemfile.lock +4 -4
  17. data/spec/support/v4.2/app/models/squib.rb +6 -0
  18. data/spec/support/v4.2/app/models/wizard.rb +1 -2
  19. data/spec/support/v4.2/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
  20. data/spec/support/v4.2/db/schema.rb +2 -1
  21. data/spec/support/v5.0/Gemfile.lock +4 -4
  22. data/spec/support/v5.0/app/models/squib.rb +6 -0
  23. data/spec/support/v5.0/app/models/wizard.rb +1 -1
  24. data/spec/support/v5.0/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
  25. data/spec/support/v5.0/db/schema.rb +4 -3
  26. data/spec/support/v5.1/Gemfile.lock +4 -4
  27. data/spec/support/v5.1/app/models/squib.rb +6 -0
  28. data/spec/support/v5.1/app/models/wizard.rb +1 -1
  29. data/spec/support/v5.1/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
  30. data/spec/support/v5.1/db/schema.rb +2 -1
  31. data/spec/support/v5.2/Gemfile.lock +4 -4
  32. data/spec/support/v5.2/app/models/squib.rb +6 -0
  33. data/spec/support/v5.2/app/models/wizard.rb +1 -1
  34. data/spec/support/v5.2/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
  35. data/spec/support/v5.2/db/schema.rb +2 -1
  36. data/spec/support/v6.0/Gemfile.lock +5 -5
  37. data/spec/support/v6.0/app/models/squib.rb +6 -0
  38. data/spec/support/v6.0/app/models/wizard.rb +1 -1
  39. data/spec/support/v6.0/db/migrate/20190620000007_add_type_to_wizard.rb +6 -0
  40. data/spec/support/v6.0/db/schema.rb +2 -1
  41. data/spec/test_data/v4.2/expected_squib.rbi +903 -0
  42. data/spec/test_data/v4.2/expected_wizard.rbi +9 -0
  43. data/spec/test_data/v4.2/expected_wizard_wo_spellbook.rbi +9 -0
  44. data/spec/test_data/v5.0/expected_squib.rbi +1164 -0
  45. data/spec/test_data/v5.0/expected_wizard.rbi +9 -0
  46. data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +9 -0
  47. data/spec/test_data/v5.1/expected_squib.rbi +1188 -0
  48. data/spec/test_data/v5.1/expected_wizard.rbi +9 -0
  49. data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +9 -0
  50. data/spec/test_data/v5.2/expected_squib.rbi +1236 -0
  51. data/spec/test_data/v5.2/expected_wizard.rbi +9 -0
  52. data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +9 -0
  53. data/spec/test_data/v6.0/expected_squib.rbi +1500 -0
  54. data/spec/test_data/v6.0/expected_wizard.rbi +9 -0
  55. data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +9 -0
  56. metadata +37 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4532cfd7b6aa106f5b870cd2b6013acd7d72644985a29cb398f0b25959b2d4b0
4
- data.tar.gz: 39329d64e2eaee0afd14a180404897c1ec0b1601a8b33bdedb6e3d3e9f12695e
3
+ metadata.gz: f4aa01ecf2a262e021765ef53cdd6d5dbaaa93776c0d45ce991f9e70943eae84
4
+ data.tar.gz: 3957eb0b707a6615466d102eb7fafc25ab21ec35a6a7b3413629ac2924cda36d
5
5
  SHA512:
6
- metadata.gz: 693c741863b4edf77cbb843b9cf030e91f098ad6291be63d2a365e845c99701b32f14a1c35658a0375aeeec25f9fcff702d0045fc36904704c3f49dd9640fca9
7
- data.tar.gz: 9985e82caf4debbbac6512698190c2f787d0e8bb51ae2853bf17a3828ee4f1921330ad86829615f3f4e503d992617f43c5b6bd1c11d32f62cb75f1d03fd267e9
6
+ metadata.gz: cf331590decda2ee306cecda5bd89b1c603b5cbbfb1000f8dfe13e2ca6277d13269e5916cf9337c855be390da9a09eb0b6dd5e36867425421adeb563f1ffafe4
7
+ data.tar.gz: 61f5427e41cbee6b74311596b8aa54aa981ddf522692c151ca6db95b0cb5fc409e0ee476aab307b6baf0eb0576f38352c2e6c65be1d1d7bd7ccd28753bc3e5a8
data/README.md CHANGED
@@ -87,6 +87,12 @@ For example:
87
87
  key = params.require_typed(:key, TA[String].new)
88
88
  T.reveal_type(key) # String
89
89
 
90
+ # nested params
91
+ nested_params = params.require_typed(:nested, TA[ActionController::Parameters].new)
92
+ T.reveal_type(nested_params) # ActionController::Parameters
93
+ key = nested_params.require_typed(:key, TA[String.new])
94
+ T.reveal_type(key) # String
95
+
90
96
  # fetch_typed
91
97
  key = params.fetch_typed(:key, TA[T.nilable(String)].new) # raises error if params doesn't have :key
92
98
  T.reveal_type(key) # T.nilable(String)
@@ -96,7 +102,7 @@ T.reveal_type(key) # T.nilable(String)
96
102
  ```
97
103
  The parameters are type-checked both statically and at runtime.
98
104
 
99
- Note: The API `TA[...].new` may seems verbose, but necessary to support this feature. Ideally, the API can be simply `require_typed(:key, Type)`. 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. The library provides a wrapper `TA` (stands for TypeAssert) in order to achieve the behavior. If it is by `sorbet` in the future, it will be easy to codemod to remove the `TA[...].new` part from your code.
105
+ Note: The API `TA[...].new` may seem verbose, but necessary to support this feature. Ideally, the API can be simply `require_typed(:key, Type)`. 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. The library provides a wrapper `TA` (which stands for `TypeAssert`) in order to achieve the behavior. If this feature is supported by `sorbet` in the future, it will be easy to codemod to remove the `TA[...].new` part from your code.
100
106
 
101
107
  ### Routes
102
108
 
@@ -4,5 +4,7 @@ module SorbetRails
4
4
  require 'sorbet-rails/railtie'
5
5
  require 'sorbet-rails/model_rbi_formatter'
6
6
  require 'sorbet-rails/type_assert/type_assert'
7
+ require 'sorbet-rails/custom_types/integer_string'
8
+ require 'sorbet-rails/custom_types/boolean_string'
7
9
  end
8
10
  end
@@ -0,0 +1,32 @@
1
+ # typed: false
2
+ module BooleanStringImpl
3
+ def is_a?(type)
4
+ return super unless type == BooleanString
5
+ _is_a_boolean_string?
6
+ end
7
+
8
+ def kind_of?(type)
9
+ return super unless type == BooleanString
10
+ _is_a_boolean_string?
11
+ end
12
+
13
+ def instance_of?(type)
14
+ return super unless type == BooleanString
15
+ _is_a_boolean_string?
16
+ end
17
+
18
+ def _is_a_boolean_string?
19
+ return @cached_is_a unless @cached_is_a.nil?
20
+ @cached_is_a = (self =~ /^(true|false)$/i) == 0
21
+ end
22
+ end
23
+
24
+ class String
25
+ include BooleanStringImpl
26
+ end
27
+
28
+ class BooleanString < String
29
+ def self.===(other)
30
+ other.is_a?(BooleanString)
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ # typed: false
2
+ module IntegerStringImpl
3
+ def is_a?(type)
4
+ return super unless type == IntegerString
5
+ _is_a_integer_string?
6
+ end
7
+
8
+ def kind_of?(type)
9
+ return super unless type == IntegerString
10
+ _is_a_integer_string?
11
+ end
12
+
13
+ def instance_of?(type)
14
+ return super unless type == IntegerString
15
+ _is_a_integer_string?
16
+ end
17
+
18
+ def _is_a_integer_string?
19
+ return @cached_is_a unless @cached_is_a.nil?
20
+ Integer(self, 10)
21
+ @cached_is_a = true
22
+ rescue ArgumentError => err
23
+ @cached_is_a = false
24
+ end
25
+ end
26
+
27
+ class String
28
+ include IntegerStringImpl
29
+ end
30
+
31
+ class IntegerString < String
32
+ def self.===(other)
33
+ other.is_a?(IntegerString)
34
+ end
35
+ end
@@ -32,7 +32,11 @@ class SorbetRails::ModelPlugins::ActiveRecordEnum < SorbetRails::ModelPlugins::B
32
32
  class_method: true,
33
33
  )
34
34
 
35
- enum_call = enum_calls.find {|call| call.has_key?(enum_name.to_sym)}
35
+ enum_call = ActiveRecordOverrides.instance.get_enum_call(@model_class, enum_name.to_sym)
36
+ if enum_call.nil?
37
+ puts "Error: unable to find enum call for enum #{enum_name}, model #{self.model_class_name}"
38
+ next
39
+ end
36
40
 
37
41
  enum_prefix = enum_call[:_prefix]
38
42
  prefix =
@@ -11,9 +11,23 @@ class ActiveRecordOverrides
11
11
  @enum_calls = {}
12
12
  end
13
13
 
14
- def store_enum_call(class_name, kwargs)
15
- @enum_calls[class_name] ||= []
16
- @enum_calls[class_name] << kwargs
14
+ def store_enum_call(klass, kwargs)
15
+ class_name = klass.name
16
+ @enum_calls[class_name] ||= {}
17
+ # modeling the logic in
18
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/enum.rb#L152
19
+ kwargs.each do |name, values|
20
+ next if [:_prefix, :_suffix, :_scopes].include?(name)
21
+ @enum_calls[class_name][name] = kwargs
22
+ end
23
+ end
24
+
25
+ def get_enum_call(klass, enum_sym)
26
+ return nil if klass == Object
27
+ class_name = klass.name
28
+ class_enum_calls = @enum_calls[klass.name]
29
+ return class_enum_calls[enum_sym] if class_enum_calls && class_enum_calls.has_key?(enum_sym)
30
+ return get_enum_call(klass.superclass, enum_sym)
17
31
  end
18
32
  end
19
33
 
@@ -21,7 +35,7 @@ module ::ActiveRecord::Enum
21
35
  alias old_enum enum
22
36
 
23
37
  def enum(*args, **kwargs)
24
- ActiveRecordOverrides.instance.store_enum_call(T.unsafe(self).name, kwargs)
38
+ ActiveRecordOverrides.instance.store_enum_call(self, kwargs)
25
39
  old_enum(*args, **kwargs)
26
40
  end
27
41
  end
@@ -134,11 +134,10 @@ namespace :rails_rbi do
134
134
  end
135
135
 
136
136
  def copy_bundled_rbi(filename)
137
- puts "rails rbi rake dir #{RAILS_RBI_RAKE_DIR}"
137
+ puts "Copy bundled file #{filename}"
138
138
  bundled_rbi_file_path = File.join(RAILS_RBI_RAKE_DIR, "..", "..", "bundled_rbi", filename)
139
139
  copy_to_path = Rails.root.join("sorbet", "rails-rbi", filename)
140
140
  FileUtils.mkdir_p(File.dirname(copy_to_path))
141
- puts "file path to copy #{bundled_rbi_file_path}"
142
141
  FileUtils.cp(bundled_rbi_file_path, copy_to_path)
143
142
  end
144
143
  end
@@ -7,7 +7,6 @@ class TA
7
7
  extend T::Sig
8
8
  extend T::Generic
9
9
  include ITypeAssert
10
- include TypeAssertImpl
11
10
 
12
11
  Elem = type_member
13
12
 
@@ -1,26 +1,26 @@
1
1
  # typed: ignore
2
2
  require 'sorbet-runtime'
3
3
 
4
- module TypeAssertImpl
5
- def self.included(klass)
6
- klass.define_singleton_method(:[]) do |*types|
7
- if types.length != 1
8
- raise Unsupported.new("TypeAssert only supports 1 type, given #{types}")
9
- end
10
- type = types.first
11
- return Class.new do
12
- include ITypeAssert
4
+ if !$PROGRAM_NAME.include?('sorbet')
5
+ module TypeAssertImpl
6
+ def self.included(klass)
7
+ klass.define_singleton_method(:[]) do |type|
8
+ return Class.new do
9
+ include ITypeAssert
13
10
 
14
- define_method(:to_s) { "TA[#{type.to_s}]" }
11
+ define_method(:to_s) { "TA[#{type.to_s}]" }
15
12
 
16
- define_method(:assert) do |val|
17
- T.let(val, type)
18
- end
13
+ define_method(:assert) do |val|
14
+ T.let(val, type)
15
+ end
19
16
 
20
- define_method(:get_type) { type }
17
+ define_method(:get_type) { type }
18
+ end
21
19
  end
22
20
  end
23
21
  end
24
22
 
25
- class Unsupported < StandardError; end
26
- end
23
+ class TA
24
+ include TypeAssertImpl
25
+ end
26
+ end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{sorbet-rails}
3
- s.version = "0.5.5"
3
+ s.version = "0.5.5.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"]
@@ -0,0 +1,59 @@
1
+ require 'rails_helper'
2
+ require 'sorbet-rails/custom_types/boolean_string'
3
+ require 'sorbet-runtime'
4
+
5
+ RSpec.describe BooleanString do
6
+ shared_examples 'boolean string' do |str|
7
+ it 'acts as BooleanString perfectly' do
8
+ expect(str.is_a?(BooleanString)).to be(true)
9
+ expect(str.kind_of?(BooleanString)).to be(true)
10
+ expect(str.instance_of?(BooleanString)).to be(true)
11
+ expect(BooleanString === str).to be(true)
12
+ end
13
+ end
14
+
15
+ include_examples 'boolean string', 'true'
16
+ include_examples 'boolean string', 'false'
17
+ include_examples 'boolean string', 'True'
18
+ include_examples 'boolean string', 'False'
19
+
20
+ shared_examples 'non boolean string' do |str|
21
+ it 'does not acts as BooleanString' do
22
+ expect(str.is_a?(BooleanString)).to be(false)
23
+ expect(str.kind_of?(BooleanString)).to be(false)
24
+ expect(str.instance_of?(BooleanString)).to be(false)
25
+ expect(BooleanString === str).to be(false)
26
+ end
27
+ end
28
+
29
+ include_examples 'non boolean string', 'yes'
30
+ include_examples 'non boolean string', 'no'
31
+ include_examples 'non boolean string', 'alala'
32
+
33
+ context 'sorbet recognizes it at runtime' do
34
+ it 'lets boolean string pass runtime check' do
35
+ expect(T.let('true', BooleanString)).to eql('true')
36
+ end
37
+
38
+ it 'doesnt let normal string pass runtime typecheck' do
39
+ expect {
40
+ T.let('yes', BooleanString)
41
+ }.to raise_error(TypeError)
42
+ end
43
+ end
44
+
45
+ context 'using with TypeAssert' do
46
+ let!(:ta) { TA[BooleanString].new }
47
+
48
+ it 'lets boolean string pass runtime typecheck' do
49
+ expect(ta.assert('true')).to eql('true')
50
+ end
51
+
52
+ it 'doesnt let normal string pass runtime typecheck' do
53
+ expect {
54
+ ta.assert('yes')
55
+ }.to raise_error(TypeError)
56
+ end
57
+ end
58
+ end
59
+
@@ -216,6 +216,14 @@ def create_models
216
216
  end
217
217
  end
218
218
  RUBY
219
+
220
+ file "app/models/squib.rb", <<~RUBY
221
+ class Squib < Wizard
222
+ def is_magical
223
+ false
224
+ end
225
+ end
226
+ RUBY
219
227
  end
220
228
 
221
229
  def create_migrations
@@ -304,6 +312,14 @@ def create_migrations
304
312
  end
305
313
  RUBY
306
314
  end
315
+
316
+ file "db/migrate/20190620000007_add_type_to_wizard.rb", <<~RUBY
317
+ class AddTypeToWizard < #{migration_superclass}
318
+ def change
319
+ add_column :wizards, :type, :string, null: false, default: 'Wizard'
320
+ end
321
+ end
322
+ RUBY
307
323
  end
308
324
 
309
325
  def create_mailers
@@ -0,0 +1,46 @@
1
+ require 'rails_helper'
2
+ require 'sorbet-rails/custom_types/integer_string'
3
+ require 'sorbet-runtime'
4
+
5
+ RSpec.describe IntegerString do
6
+ it 'lets integer string acts as IntegerString perfectly' do
7
+ expect('1'.is_a?(IntegerString)).to be(true)
8
+ expect('12'.kind_of?(IntegerString)).to be(true)
9
+ expect('123'.instance_of?(IntegerString)).to be(true)
10
+ expect(IntegerString === '1234').to be(true)
11
+ end
12
+
13
+ it 'does not let other string acts as IntegerString' do
14
+ expect('a1'.is_a?(IntegerString)).to be(false)
15
+ expect('a12'.kind_of?(IntegerString)).to be(false)
16
+ expect('a123'.instance_of?(IntegerString)).to be(false)
17
+ expect(IntegerString === 'a1234').to be(false)
18
+ end
19
+
20
+ context 'sorbet recognizes it at runtime' do
21
+ it 'lets integer string pass runtime check' do
22
+ expect(T.let('123', IntegerString)).to eql('123')
23
+ end
24
+
25
+ it 'doesnt let normal string pass runtime typecheck' do
26
+ expect {
27
+ T.let('a123', IntegerString)
28
+ }.to raise_error(TypeError)
29
+ end
30
+ end
31
+
32
+ context 'using with TypeAssert' do
33
+ let!(:ta) { TA[IntegerString].new }
34
+
35
+ it 'lets integer string pass runtime typecheck' do
36
+ expect(ta.assert('123')).to eql('123')
37
+ end
38
+
39
+ it 'doesnt let normal string pass runtime typecheck' do
40
+ expect {
41
+ ta.assert('a123')
42
+ }.to raise_error(TypeError)
43
+ end
44
+ end
45
+ end
46
+
@@ -14,6 +14,7 @@ RSpec.describe 'rake rails_rbi:models', type: :task do
14
14
  files: [
15
15
  'potion.rbi',
16
16
  'spell_book.rbi',
17
+ 'squib.rbi',
17
18
  'wand.rbi',
18
19
  'wizard.rbi',
19
20
  ]
@@ -95,10 +95,10 @@ GEM
95
95
  thor (>= 0.18.1, < 2.0)
96
96
  rainbow (3.0.0)
97
97
  rake (12.3.3)
98
- sorbet (0.4.4709)
99
- sorbet-static (= 0.4.4709)
100
- sorbet-runtime (0.4.4709)
101
- sorbet-static (0.4.4709-x86_64-linux)
98
+ sorbet (0.4.4755)
99
+ sorbet-static (= 0.4.4755)
100
+ sorbet-runtime (0.4.4755)
101
+ sorbet-static (0.4.4755-x86_64-linux)
102
102
  sprockets (3.7.2)
103
103
  concurrent-ruby (~> 1.0)
104
104
  rack (> 1, < 3)
@@ -0,0 +1,6 @@
1
+ # typed: true
2
+ class Squib < Wizard
3
+ def is_magical
4
+ false
5
+ end
6
+ end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: ignore
2
2
  class Wizard < ApplicationRecord
3
3
  validates :name, length: { minimum: 5 }, presence: true
4
4
 
@@ -26,5 +26,4 @@ class Wizard < ApplicationRecord
26
26
  has_many :spell_books
27
27
 
28
28
  scope :recent, -> { where('created_at > ?', 1.month.ago) }
29
-
30
29
  end
@@ -0,0 +1,6 @@
1
+ # typed: false
2
+ class AddTypeToWizard < ActiveRecord::Migration
3
+ def change
4
+ add_column :wizards, :type, :string, null: false, default: 'Wizard'
5
+ end
6
+ end
@@ -12,7 +12,7 @@
12
12
  #
13
13
  # It's strongly recommended that you check this file into your version control system.
14
14
 
15
- ActiveRecord::Schema.define(version: 20190620000005) do
15
+ ActiveRecord::Schema.define(version: 20190620000007) do
16
16
 
17
17
  create_table "spell_books", force: :cascade do |t|
18
18
  t.string "name"
@@ -42,6 +42,7 @@ ActiveRecord::Schema.define(version: 20190620000005) do
42
42
  t.datetime "created_at"
43
43
  t.datetime "updated_at"
44
44
  t.string "broom"
45
+ t.string "type", default: "Wizard", null: false
45
46
  end
46
47
 
47
48
  end