sorbet-rails 0.5.5 → 0.5.5.1

Sign up to get free protection for your applications and to get access to all the features.
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