sorbet-rails 0.7.2 → 0.7.3
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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/lib/sorbet-rails/config.rb +1 -0
- data/lib/sorbet-rails/dependent_gem_rbis/activerecord.rbi +3 -0
- data/lib/sorbet-rails/model_plugins/active_record_assoc.rb +17 -5
- data/lib/sorbet-rails/model_plugins/active_record_attribute.rb +2 -0
- data/lib/sorbet-rails/model_plugins/active_record_serialized_attribute.rb +68 -0
- data/lib/sorbet-rails/model_plugins/base.rb +8 -0
- data/lib/sorbet-rails/model_plugins/plugins.rb +3 -0
- data/lib/sorbet-rails/model_rbi_formatter.rb +3 -3
- data/lib/sorbet-rails/rails_mixins/pluck_to_tstruct.rb +39 -2
- data/lib/sorbet-rails/routes_rbi_formatter.rb +4 -0
- data/lib/sorbet-rails/tasks/rails_rbi.rake +4 -3
- data/sorbet-rails.gemspec +1 -1
- data/spec/bin/run_spec.sh +1 -1
- data/spec/generators/rails-template.rb +16 -0
- data/spec/generators/sorbet_test_cases.rb +15 -0
- data/spec/pluck_to_tstruct_spec.rb +51 -25
- data/spec/sorbet_spec.rb +1 -1
- data/spec/support/v5.0/Gemfile.lock +4 -4
- data/spec/support/v5.0/app/controllers/application_controller.rb +1 -1
- data/spec/support/v5.0/app/models/headmaster.rb +1 -1
- data/spec/support/v5.0/app/models/potion.rb +1 -1
- data/spec/support/v5.0/app/models/robe.rb +1 -1
- data/spec/support/v5.0/app/models/school.rb +1 -1
- data/spec/support/v5.0/app/models/spell.rb +1 -1
- data/spec/support/v5.0/app/models/subject.rb +1 -1
- data/spec/support/v5.0/app/models/wizard.rb +5 -0
- data/spec/support/v5.0/config/environments/development.rb +1 -1
- data/spec/support/v5.0/config/environments/production.rb +1 -1
- data/spec/support/v5.0/config/environments/test.rb +1 -1
- data/spec/support/v5.0/config/initializers/wrap_parameters.rb +1 -1
- data/spec/support/v5.0/config/routes.rb +1 -1
- data/spec/support/v5.0/db/migrate/20190620000015_add_serialized_to_wizards.rb +9 -0
- data/spec/support/v5.0/db/schema.rb +8 -4
- data/spec/support/v5.0/sorbet_test_cases.rb +15 -0
- data/spec/support/v5.1/Gemfile.lock +4 -4
- data/spec/support/v5.1/app/controllers/application_controller.rb +1 -1
- data/spec/support/v5.1/app/models/headmaster.rb +1 -1
- data/spec/support/v5.1/app/models/school.rb +1 -1
- data/spec/support/v5.1/app/models/wizard.rb +5 -0
- data/spec/support/v5.1/config/environments/production.rb +1 -1
- data/spec/support/v5.1/config/initializers/wrap_parameters.rb +1 -1
- data/spec/support/v5.1/config/routes.rb +1 -1
- data/spec/support/v5.1/db/migrate/20190620000015_add_serialized_to_wizards.rb +9 -0
- data/spec/support/v5.1/db/schema.rb +5 -1
- data/spec/support/v5.1/sorbet_test_cases.rb +15 -0
- data/spec/support/v5.2/Gemfile.lock +4 -4
- data/spec/support/v5.2/app/models/headmaster.rb +1 -1
- data/spec/support/v5.2/app/models/school.rb +1 -1
- data/spec/support/v5.2/app/models/wizard.rb +5 -0
- data/spec/support/v5.2/config/environments/development.rb +1 -1
- data/spec/support/v5.2/config/environments/production.rb +1 -1
- data/spec/support/v5.2/config/environments/test.rb +1 -1
- data/spec/support/v5.2/config/initializers/wrap_parameters.rb +1 -1
- data/spec/support/v5.2/config/routes.rb +1 -1
- data/spec/support/v5.2/db/migrate/20190620000015_add_serialized_to_wizards.rb +9 -0
- data/spec/support/v5.2/db/schema.rb +5 -1
- data/spec/support/v5.2/sorbet_test_cases.rb +15 -0
- data/spec/support/v6.0/Gemfile.lock +4 -4
- data/spec/support/v6.0/app/models/wizard.rb +5 -0
- data/spec/support/v6.0/config/environments/development.rb +1 -1
- data/spec/support/v6.0/config/environments/production.rb +1 -1
- data/spec/support/v6.0/config/environments/test.rb +1 -1
- data/spec/support/v6.0/config/routes.rb +1 -1
- data/spec/support/v6.0/db/migrate/20190620000015_add_serialized_to_wizards.rb +9 -0
- data/spec/support/v6.0/db/schema.rb +5 -1
- data/spec/support/v6.0/sorbet_test_cases.rb +15 -0
- data/spec/test_data/v5.0/expected_headmaster.rbi +6 -0
- data/spec/test_data/v5.0/expected_potion.rbi +3 -0
- data/spec/test_data/v5.0/expected_robe.rbi +3 -0
- data/spec/test_data/v5.0/expected_routes.rbi +4 -0
- data/spec/test_data/v5.0/expected_school.rbi +3 -0
- data/spec/test_data/v5.0/expected_spell/habtm_spell_books.rbi +6 -0
- data/spec/test_data/v5.0/expected_spell_book.rbi +3 -0
- data/spec/test_data/v5.0/expected_spell_book/habtm_spells.rbi +6 -0
- data/spec/test_data/v5.0/expected_squib.rbi +45 -0
- data/spec/test_data/v5.0/expected_subject/habtm_wizards.rbi +6 -0
- data/spec/test_data/v5.0/expected_wand.rbi +3 -0
- data/spec/test_data/v5.0/expected_wizard.rbi +45 -0
- data/spec/test_data/v5.0/expected_wizard/habtm_subjects.rbi +6 -0
- data/spec/test_data/v5.0/expected_wizard_wo_spellbook.rbi +45 -0
- data/spec/test_data/v5.1/expected_headmaster.rbi +6 -0
- data/spec/test_data/v5.1/expected_potion.rbi +3 -0
- data/spec/test_data/v5.1/expected_robe.rbi +3 -0
- data/spec/test_data/v5.1/expected_routes.rbi +4 -0
- data/spec/test_data/v5.1/expected_school.rbi +3 -0
- data/spec/test_data/v5.1/expected_spell/habtm_spell_books.rbi +6 -0
- data/spec/test_data/v5.1/expected_spell_book.rbi +3 -0
- data/spec/test_data/v5.1/expected_spell_book/habtm_spells.rbi +6 -0
- data/spec/test_data/v5.1/expected_squib.rbi +45 -0
- data/spec/test_data/v5.1/expected_subject/habtm_wizards.rbi +6 -0
- data/spec/test_data/v5.1/expected_wand.rbi +3 -0
- data/spec/test_data/v5.1/expected_wizard.rbi +45 -0
- data/spec/test_data/v5.1/expected_wizard/habtm_subjects.rbi +6 -0
- data/spec/test_data/v5.1/expected_wizard_wo_spellbook.rbi +45 -0
- data/spec/test_data/v5.2/expected_attachment.rbi +6 -0
- data/spec/test_data/v5.2/expected_blob.rbi +6 -0
- data/spec/test_data/v5.2/expected_headmaster.rbi +6 -0
- data/spec/test_data/v5.2/expected_potion.rbi +3 -0
- data/spec/test_data/v5.2/expected_robe.rbi +3 -0
- data/spec/test_data/v5.2/expected_routes.rbi +4 -0
- data/spec/test_data/v5.2/expected_school.rbi +3 -0
- data/spec/test_data/v5.2/expected_spell/habtm_spell_books.rbi +6 -0
- data/spec/test_data/v5.2/expected_spell_book.rbi +3 -0
- data/spec/test_data/v5.2/expected_spell_book/habtm_spells.rbi +6 -0
- data/spec/test_data/v5.2/expected_squib.rbi +51 -0
- data/spec/test_data/v5.2/expected_subject/habtm_wizards.rbi +6 -0
- data/spec/test_data/v5.2/expected_wand.rbi +3 -0
- data/spec/test_data/v5.2/expected_wizard.rbi +51 -0
- data/spec/test_data/v5.2/expected_wizard/habtm_subjects.rbi +6 -0
- data/spec/test_data/v5.2/expected_wizard_wo_spellbook.rbi +51 -0
- data/spec/test_data/v6.0/expected_attachment.rbi +6 -0
- data/spec/test_data/v6.0/expected_blob.rbi +6 -0
- data/spec/test_data/v6.0/expected_headmaster.rbi +6 -0
- data/spec/test_data/v6.0/expected_potion.rbi +3 -0
- data/spec/test_data/v6.0/expected_robe.rbi +3 -0
- data/spec/test_data/v6.0/expected_routes.rbi +4 -0
- data/spec/test_data/v6.0/expected_school.rbi +3 -0
- data/spec/test_data/v6.0/expected_spell/habtm_spell_books.rbi +6 -0
- data/spec/test_data/v6.0/expected_spell_book.rbi +3 -0
- data/spec/test_data/v6.0/expected_spell_book/habtm_spells.rbi +6 -0
- data/spec/test_data/v6.0/expected_squib.rbi +51 -0
- data/spec/test_data/v6.0/expected_subject/habtm_wizards.rbi +6 -0
- data/spec/test_data/v6.0/expected_wand.rbi +3 -0
- data/spec/test_data/v6.0/expected_wizard.rbi +51 -0
- data/spec/test_data/v6.0/expected_wizard/habtm_subjects.rbi +6 -0
- data/spec/test_data/v6.0/expected_wizard_wo_spellbook.rbi +51 -0
- data/spec/tstruct_comparable.rb +13 -0
- metadata +12 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04e15a0b91862375e9b3f853928e0f46a3c44e6176cb10ecb813a337ac50a45a
|
4
|
+
data.tar.gz: e6a5b10b3ac40664e0dce8a51c80795d8fd2e0c7ef4cc665c6ad169d6cd3f0dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b3bddd29abcb7d6496ece148526fc204c9a7e2bd51f54a264d526e3c10bcacafe6118a49e9ad9c36f6edb9927fe80cd31de28ce4ddda035811f5d15520e2599
|
7
|
+
data.tar.gz: '05249019b6251b14ab95a71599c666710478c8468702546c0619ff9111021447d246ea47bc9f62bb038e04616e6b306647b4c669315ff59672b93a1682e6eb39'
|
data/.travis.yml
CHANGED
data/lib/sorbet-rails/config.rb
CHANGED
@@ -12,6 +12,9 @@ class ActiveRecord::Base < Object
|
|
12
12
|
sig { returns(T::Hash[String, T.untyped]) }
|
13
13
|
def self.columns_hash; end
|
14
14
|
|
15
|
+
sig { params(column_name: String).returns(T.nilable(T.any(ActiveModel::Type::Value, ActiveRecord::Type::Serialized))) }
|
16
|
+
def self.type_for_attribute(column_name); end
|
17
|
+
|
15
18
|
sig { returns(T::Hash[String, T.untyped]) }
|
16
19
|
def self.reflections; end
|
17
20
|
|
@@ -67,6 +67,10 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
67
67
|
],
|
68
68
|
return_type: nil,
|
69
69
|
)
|
70
|
+
assoc_module_rbi.create_method(
|
71
|
+
"reload_#{assoc_name}",
|
72
|
+
return_type: assoc_type,
|
73
|
+
)
|
70
74
|
end
|
71
75
|
|
72
76
|
sig { params(reflection: T.untyped).returns(T::Boolean) }
|
@@ -134,13 +138,21 @@ class SorbetRails::ModelPlugins::ActiveRecordAssoc < SorbetRails::ModelPlugins::
|
|
134
138
|
return_type: relation_class,
|
135
139
|
)
|
136
140
|
unless assoc_should_be_untyped?(reflection)
|
141
|
+
id_type = "T.untyped"
|
142
|
+
|
137
143
|
if reflection.klass.table_exists?
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
144
|
+
# For DB views, the PK column would not exist.
|
145
|
+
id_column = reflection.klass.primary_key
|
146
|
+
|
147
|
+
if id_column
|
148
|
+
id_column_def = reflection.klass.columns_hash[id_column]
|
149
|
+
|
150
|
+
# Normally the id_type is an Integer, but it could be a String if using
|
151
|
+
# UUIDs.
|
152
|
+
id_type = type_for_column_def(id_column_def).to_s if id_column_def
|
153
|
+
end
|
143
154
|
end
|
155
|
+
|
144
156
|
assoc_module_rbi.create_method(
|
145
157
|
"#{assoc_name.singularize}_ids",
|
146
158
|
return_type: "T::Array[#{id_type}]",
|
@@ -24,6 +24,8 @@ class SorbetRails::ModelPlugins::ActiveRecordAttribute < SorbetRails::ModelPlugi
|
|
24
24
|
column_name,
|
25
25
|
column_def,
|
26
26
|
)
|
27
|
+
elsif serialization_coder_for_column(column_name)
|
28
|
+
next # handled by the ActiveRecordSerializedAttribute plugin
|
27
29
|
else
|
28
30
|
column_type = type_for_column_def(column_def)
|
29
31
|
attribute_module_rbi.create_method(
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: strict
|
2
|
+
require ('sorbet-rails/model_plugins/base')
|
3
|
+
class SorbetRails::ModelPlugins::ActiveRecordSerializedAttribute < SorbetRails::ModelPlugins::Base
|
4
|
+
|
5
|
+
sig { override.params(root: Parlour::RbiGenerator::Namespace).void }
|
6
|
+
def generate(root)
|
7
|
+
columns_hash = @model_class.table_exists? ? @model_class.columns_hash : {}
|
8
|
+
return unless any_serialized_columns?(columns_hash)
|
9
|
+
|
10
|
+
serialize_module_name = self.model_module_name('GeneratedSerializedAttributeMethods')
|
11
|
+
serialize_module_rbi = root.create_module(serialize_module_name)
|
12
|
+
|
13
|
+
model_class_rbi = root.create_class(self.model_class_name)
|
14
|
+
model_class_rbi.create_include(serialize_module_name)
|
15
|
+
|
16
|
+
columns_hash.sort.each do |column_name, column_def|
|
17
|
+
serialization_coder = serialization_coder_for_column(column_name)
|
18
|
+
next unless serialization_coder
|
19
|
+
|
20
|
+
nilable = nilable_column?(column_def)
|
21
|
+
attr_type = attr_types_for_coder(serialization_coder)
|
22
|
+
|
23
|
+
serialize_module_rbi.create_method(
|
24
|
+
column_name.to_s,
|
25
|
+
return_type: ColumnType.new(base_type: attr_type, nilable: nilable).to_s,
|
26
|
+
)
|
27
|
+
|
28
|
+
serialize_module_rbi.create_method(
|
29
|
+
"#{column_name}=",
|
30
|
+
parameters: [
|
31
|
+
Parameter.new('value', type: ColumnType.new(base_type: attr_type, nilable: nilable).to_s)
|
32
|
+
],
|
33
|
+
return_type: nil,
|
34
|
+
)
|
35
|
+
|
36
|
+
serialize_module_rbi.create_method(
|
37
|
+
"#{column_name}?",
|
38
|
+
return_type: 'T::Boolean',
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { params(columns_hash: T::Hash[String, ActiveRecord::ConnectionAdapters::Column]).returns(T::Boolean) }
|
44
|
+
def any_serialized_columns?(columns_hash)
|
45
|
+
columns_hash.keys.any? do |column_name|
|
46
|
+
!serialization_coder_for_column(column_name).nil?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { params(serialization_coder: T.nilable(Class)).returns(String) }
|
51
|
+
def attr_types_for_coder(serialization_coder)
|
52
|
+
case serialization_coder.to_s
|
53
|
+
when 'Hash'
|
54
|
+
# Hash uses YAML.load/YAML.dump, and permits pretty much any kind of Hash key
|
55
|
+
'T::Hash[T.untyped, T.untyped]'
|
56
|
+
when 'Array'
|
57
|
+
# YAML.load/YAML.dump
|
58
|
+
'T::Array[T.untyped]'
|
59
|
+
when 'JSON'
|
60
|
+
# ActiveSupport::JSON.encode/ActiveSupport::JSON.decode
|
61
|
+
# note that Hash keys are Strings since this is JSON
|
62
|
+
'T.any(T::Array[T.untyped], T::Boolean, Float, T::Hash[String, T.untyped], Integer, String)'
|
63
|
+
else
|
64
|
+
# unknown uses YAML.load/YAML.dump
|
65
|
+
'T.any(T::Array[T.untyped], T::Boolean, Float, T::Hash[T.untyped, T.untyped], Integer, String)'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -29,5 +29,13 @@ module SorbetRails::ModelPlugins
|
|
29
29
|
@model_class = T.let(model_class, T.class_of(ActiveRecord::Base))
|
30
30
|
@available_classes = T.let(available_classes, T::Set[String])
|
31
31
|
end
|
32
|
+
|
33
|
+
sig { params(column_name: String).returns(T.nilable(Class)) }
|
34
|
+
def serialization_coder_for_column(column_name)
|
35
|
+
column_type = @model_class.type_for_attribute(column_name)
|
36
|
+
return unless column_type.is_a?(ActiveRecord::Type::Serialized)
|
37
|
+
|
38
|
+
column_type.coder.try(:object_class) || Object
|
39
|
+
end
|
32
40
|
end
|
33
41
|
end
|
@@ -6,6 +6,7 @@ require('sorbet-rails/model_plugins/active_relation_where_not')
|
|
6
6
|
require('sorbet-rails/model_plugins/active_record_named_scope')
|
7
7
|
require('sorbet-rails/model_plugins/active_record_attribute')
|
8
8
|
require('sorbet-rails/model_plugins/active_record_assoc')
|
9
|
+
require('sorbet-rails/model_plugins/active_record_serialized_attribute')
|
9
10
|
require('sorbet-rails/model_plugins/custom_finder_methods')
|
10
11
|
require('sorbet-rails/model_plugins/enumerable_collections')
|
11
12
|
require('sorbet-rails/model_plugins/active_storage_methods')
|
@@ -47,6 +48,8 @@ module SorbetRails::ModelPlugins
|
|
47
48
|
ActiveRecordQuerying
|
48
49
|
when :active_relation_where_not
|
49
50
|
ActiveRelationWhereNot
|
51
|
+
when :active_record_serialized_attribute
|
52
|
+
ActiveRecordSerializedAttribute
|
50
53
|
when :active_record_attribute
|
51
54
|
ActiveRecordAttribute
|
52
55
|
when :active_record_assoc
|
@@ -42,7 +42,7 @@ class SorbetRails::ModelRbiFormatter
|
|
42
42
|
end
|
43
43
|
|
44
44
|
generator = Parlour::RbiGenerator.new(break_params: 3)
|
45
|
-
run_plugins(plugin_instances, generator
|
45
|
+
run_plugins(plugin_instances, generator)
|
46
46
|
# Generate the base after the plugins because when ConflictResolver merge the modules,
|
47
47
|
# it'll put the modules at the last position merged. Putting the base stuff
|
48
48
|
# last will keep the order consistent and minimize changes when new plugins are added.
|
@@ -115,11 +115,11 @@ class SorbetRails::ModelRbiFormatter
|
|
115
115
|
params(
|
116
116
|
plugins: T::Array[Parlour::Plugin],
|
117
117
|
generator: Parlour::RbiGenerator,
|
118
|
-
allow_failure: T::Boolean,
|
119
118
|
).
|
120
119
|
void
|
121
120
|
}
|
122
|
-
def run_plugins(plugins, generator
|
121
|
+
def run_plugins(plugins, generator)
|
122
|
+
allow_failure = ENV["SBR_DEBUG_MODE"] == "true"
|
123
123
|
plugins.each do |plugin|
|
124
124
|
begin
|
125
125
|
generator.current_plugin = plugin
|
@@ -3,6 +3,9 @@ require 'sorbet-runtime'
|
|
3
3
|
|
4
4
|
module SorbetRails::PluckToTStruct
|
5
5
|
extend T::Sig
|
6
|
+
|
7
|
+
NILCLASS_STRING = "NilClass".freeze
|
8
|
+
|
6
9
|
sig {
|
7
10
|
type_parameters(:U).
|
8
11
|
params(
|
@@ -18,7 +21,8 @@ module SorbetRails::PluckToTStruct
|
|
18
21
|
raise UnexpectedType.new("pluck_to_tstruct expects a tstruct subclass, given #{tstruct}")
|
19
22
|
end
|
20
23
|
|
21
|
-
|
24
|
+
tstruct_props = tstruct.props
|
25
|
+
tstruct_keys = tstruct_props.keys
|
22
26
|
associations_keys = associations.keys
|
23
27
|
invalid_keys = associations_keys - tstruct_keys
|
24
28
|
|
@@ -33,11 +37,44 @@ module SorbetRails::PluckToTStruct
|
|
33
37
|
keys_one = pluck_keys.size == 1
|
34
38
|
pluck(*pluck_keys).map do |row|
|
35
39
|
row = [row] if keys_one
|
36
|
-
value = Hash[tstruct_keys.zip(row)]
|
40
|
+
value = Hash[map_nil_values_to_default(tstruct_props, tstruct_keys.zip(row))]
|
37
41
|
tstruct.new(value)
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
45
|
+
sig {params(type_object: T::Types::Base).returns(T::Boolean)}
|
46
|
+
private def nilable?(type_object)
|
47
|
+
return false unless type_object.is_a?(T::Types::Union)
|
48
|
+
|
49
|
+
type_object.types.any? { |type| type.name == NILCLASS_STRING }
|
50
|
+
end
|
51
|
+
|
52
|
+
sig {
|
53
|
+
params(
|
54
|
+
tstruct_props: T::Hash[Symbol, T.untyped],
|
55
|
+
zipped_rows: T::Array[[Symbol, T.untyped]]
|
56
|
+
)
|
57
|
+
.returns(T::Array[[Symbol, T.untyped]])
|
58
|
+
}
|
59
|
+
private def map_nil_values_to_default(tstruct_props, zipped_rows)
|
60
|
+
# we use the default value defined on a prop if
|
61
|
+
# 1. the plucked value is nil
|
62
|
+
# 2. the prop has a default
|
63
|
+
# 3. the prop's type isn't nilable
|
64
|
+
|
65
|
+
zipped_rows.map do |key, value|
|
66
|
+
next [key, value] unless value.nil?
|
67
|
+
|
68
|
+
default = tstruct_props.dig(key, :default)
|
69
|
+
next [key, value] unless default
|
70
|
+
|
71
|
+
type_object = tstruct_props.dig(key, :type_object)
|
72
|
+
next [key, value] if nilable?(type_object)
|
73
|
+
|
74
|
+
[key, default]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
41
78
|
class UnexpectedType < StandardError; end
|
42
79
|
class UnexpectedAssociations < StandardError; end
|
43
80
|
end
|
@@ -28,6 +28,10 @@ class SorbetRails::RoutesRbiFormatter
|
|
28
28
|
klass.create_include('GeneratedUrlHelpers')
|
29
29
|
end
|
30
30
|
|
31
|
+
@parlour.root.create_class('ActionController::API') do |klass|
|
32
|
+
klass.create_include('GeneratedUrlHelpers')
|
33
|
+
end
|
34
|
+
|
31
35
|
@parlour.root.create_module('ActionView::Helpers') do |mod|
|
32
36
|
mod.create_include('GeneratedUrlHelpers')
|
33
37
|
end
|
@@ -97,17 +97,18 @@ namespace :rails_rbi do
|
|
97
97
|
task helpers: :environment do |t, args|
|
98
98
|
SorbetRails::Utils.rails_eager_load_all!
|
99
99
|
|
100
|
-
|
100
|
+
# API controller does not include ActionController::Helpers
|
101
|
+
if ApplicationController < ActionController::Helpers
|
101
102
|
helpers = ApplicationController.modules_for_helpers([:all])
|
102
103
|
end
|
103
104
|
|
104
105
|
# If ApplicationController doesn't work or doesn't return any helpers,
|
105
106
|
# try using ActionController::Base.
|
106
|
-
if ActionController::
|
107
|
+
if ApplicationController < ActionController::Helpers && helpers.blank?
|
107
108
|
helpers = ActionController::Base.modules_for_helpers([:all])
|
108
109
|
end
|
109
110
|
|
110
|
-
if helpers.
|
111
|
+
if helpers.blank?
|
111
112
|
puts 'No helpers found.'
|
112
113
|
else
|
113
114
|
formatter = SorbetRails::HelperRbiFormatter.new(helpers)
|
data/sorbet-rails.gemspec
CHANGED
data/spec/bin/run_spec.sh
CHANGED
@@ -30,5 +30,5 @@ if [[ -z $RAILS_VERSION ]]; then
|
|
30
30
|
else
|
31
31
|
echo "---- Run $RAILS_VERSION ----"
|
32
32
|
RAILS_VERSION=$RAILS_VERSION bundle update
|
33
|
-
RAILS_VERSION=$RAILS_VERSION DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake
|
33
|
+
RAILS_VERSION=$RAILS_VERSION DISABLE_DATABASE_ENVIRONMENT_CHECK=1 SBR_DEBUG_MODE=true bundle exec rake
|
34
34
|
fi
|
@@ -180,6 +180,11 @@ def create_models
|
|
180
180
|
blue: 2,
|
181
181
|
}, _prefix: :color, _suffix: :eyes
|
182
182
|
|
183
|
+
serialize :owl_results, Hash
|
184
|
+
serialize :newt_subjects # no specific data type, uses the default YAML Object coder
|
185
|
+
serialize :pets, Array
|
186
|
+
serialize :patronus_characteristics, JSON
|
187
|
+
|
183
188
|
has_one :wand
|
184
189
|
has_many :spell_books
|
185
190
|
# habtm which is optional at the db level
|
@@ -417,6 +422,17 @@ def create_migrations
|
|
417
422
|
end
|
418
423
|
RUBY
|
419
424
|
|
425
|
+
file "db/migrate/20190620000015_add_serialized_to_wizards.rb", <<~RUBY
|
426
|
+
class AddSerializedToWizards < #{migration_superclass}
|
427
|
+
def change
|
428
|
+
add_column :wizards, :owl_results, :text # Hash
|
429
|
+
add_column :wizards, :newt_subjects, :text # generic
|
430
|
+
add_column :wizards, :pets, :text # Array
|
431
|
+
add_column :wizards, :patronus_characteristics, :text # serialized as JSON, but not a JSON column type
|
432
|
+
end
|
433
|
+
end
|
434
|
+
RUBY
|
435
|
+
|
420
436
|
end
|
421
437
|
|
422
438
|
def create_mailers
|
@@ -305,6 +305,21 @@ T.assert_type!(Wizard::House::Hufflepuff, Wizard::House)
|
|
305
305
|
T.assert_type!(Wizard::House::Ravenclaw, Wizard::House)
|
306
306
|
T.assert_type!(Wizard::House::Slytherin, Wizard::House)
|
307
307
|
|
308
|
+
# Serialization
|
309
|
+
T.assert_type!(
|
310
|
+
wizard.owl_results,
|
311
|
+
T.nilable(T::Hash[T.untyped, T.untyped])
|
312
|
+
)
|
313
|
+
T.assert_type!(
|
314
|
+
wizard.newt_subjects,
|
315
|
+
T.nilable(T.any(T::Array[T.untyped], T::Boolean, Float, T::Hash[T.untyped, T.untyped], Integer, String))
|
316
|
+
)
|
317
|
+
T.assert_type!(
|
318
|
+
wizard.patronus_characteristics,
|
319
|
+
T.nilable(T.any(T::Array[T.untyped], T::Boolean, Float, T::Hash[String, T.untyped], Integer, String))
|
320
|
+
)
|
321
|
+
T.assert_type!(wizard.pets, T.nilable(T::Array[T.untyped]))
|
322
|
+
|
308
323
|
# Mythical plugin
|
309
324
|
T.assert_type!(Wand.mythicals, T::Array[Wand])
|
310
325
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rails_helper'
|
2
2
|
require 'sorbet-rails/model_rbi_formatter'
|
3
|
+
require 'tstruct_comparable'
|
3
4
|
|
4
5
|
RSpec.describe SorbetRails::PluckToTStruct do
|
5
6
|
let!(:harry) do
|
@@ -41,45 +42,40 @@ RSpec.describe SorbetRails::PluckToTStruct do
|
|
41
42
|
end
|
42
43
|
|
43
44
|
class WizardName < T::Struct
|
44
|
-
|
45
|
-
|
46
|
-
def ==(other)
|
47
|
-
return false unless other.is_a?(self.class)
|
48
|
-
name == other.name
|
49
|
-
end
|
45
|
+
include TStructComparable
|
50
46
|
|
51
|
-
|
52
|
-
self == other
|
53
|
-
end
|
47
|
+
const :name, String
|
54
48
|
end
|
55
49
|
|
56
50
|
class WizardT < T::Struct
|
51
|
+
include TStructComparable
|
52
|
+
|
57
53
|
const :name, String
|
58
54
|
const :house, String
|
59
|
-
|
60
|
-
def ==(other)
|
61
|
-
return false unless other.is_a?(self.class)
|
62
|
-
name == other.name && house == other.house
|
63
|
-
end
|
64
|
-
|
65
|
-
def eql?(other)
|
66
|
-
self == other
|
67
|
-
end
|
68
55
|
end
|
69
56
|
|
70
57
|
class WizardWithWandT < T::Struct
|
58
|
+
include TStructComparable
|
59
|
+
|
71
60
|
const :name, String
|
72
61
|
const :house, String
|
73
62
|
const :wand_wood_type, String
|
63
|
+
end
|
74
64
|
|
75
|
-
|
76
|
-
|
77
|
-
name == other.name && house == other.house
|
78
|
-
end
|
65
|
+
class WizardWithDefaultParentEmailT < T::Struct
|
66
|
+
include TStructComparable
|
79
67
|
|
80
|
-
|
81
|
-
|
82
|
-
|
68
|
+
const :name, String
|
69
|
+
const :house, String
|
70
|
+
const :parent_email, String, default: "hagrid@hogwarts.com"
|
71
|
+
end
|
72
|
+
|
73
|
+
class WizardWithDefaultNilableParentEmailT < T::Struct
|
74
|
+
include TStructComparable
|
75
|
+
|
76
|
+
const :name, String
|
77
|
+
const :house, String
|
78
|
+
const :parent_email, T.nilable(String), default: "hagrid@hogwarts.com"
|
83
79
|
end
|
84
80
|
|
85
81
|
shared_examples 'pluck_to_tstruct' do |struct_type, expected_values|
|
@@ -149,4 +145,34 @@ RSpec.describe SorbetRails::PluckToTStruct do
|
|
149
145
|
|
150
146
|
it_should_behave_like 'pluck_to_tstruct with associations', WizardWithWandT, associations, expected
|
151
147
|
end
|
148
|
+
|
149
|
+
context 'uses default value if prop type is not nilable and has default value' do
|
150
|
+
it_should_behave_like 'pluck_to_tstruct', WizardWithDefaultParentEmailT, [
|
151
|
+
WizardWithDefaultParentEmailT.new(
|
152
|
+
name: "Harry Potter",
|
153
|
+
house: "Gryffindor",
|
154
|
+
parent_email: "hagrid@hogwarts.com"
|
155
|
+
),
|
156
|
+
WizardWithDefaultParentEmailT.new(
|
157
|
+
name: "Hermione Granger",
|
158
|
+
house: "Gryffindor",
|
159
|
+
parent_email: "hagrid@hogwarts.com"
|
160
|
+
),
|
161
|
+
]
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'doesnt use default value if prop type is nilable even with a default value' do
|
165
|
+
it_should_behave_like 'pluck_to_tstruct', WizardWithDefaultNilableParentEmailT, [
|
166
|
+
WizardWithDefaultNilableParentEmailT.new(
|
167
|
+
name: "Harry Potter",
|
168
|
+
house: "Gryffindor",
|
169
|
+
parent_email: nil
|
170
|
+
),
|
171
|
+
WizardWithDefaultNilableParentEmailT.new(
|
172
|
+
name: "Hermione Granger",
|
173
|
+
house: "Gryffindor",
|
174
|
+
parent_email: nil
|
175
|
+
),
|
176
|
+
]
|
177
|
+
end
|
152
178
|
end
|