jsonapi-resources-anchor 2.12.0 → 2.13.0
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/lib/anchor/concerns/typeable.rb +26 -0
- data/lib/anchor/config.rb +5 -1
- data/lib/anchor/inference/active_record/infer/base.rb +12 -0
- data/lib/anchor/inference/active_record/infer/columns.rb +42 -0
- data/lib/anchor/inference/active_record/infer/enums.rb +17 -0
- data/lib/anchor/inference/active_record/infer/model.rb +64 -0
- data/lib/anchor/inference/active_record/infer/rbs.rb +30 -0
- data/lib/anchor/inference/active_record/infer.rb +8 -0
- data/lib/anchor/inference/active_record/types/base.rb +9 -0
- data/lib/anchor/inference/active_record/types/column_comments.rb +29 -0
- data/lib/anchor/inference/active_record/types/defaulted.rb +17 -0
- data/lib/anchor/inference/active_record/types/overridden.rb +23 -0
- data/lib/anchor/inference/active_record/types/presence_required.rb +33 -0
- data/lib/anchor/inference/active_record/types/serialized.rb +13 -0
- data/lib/anchor/inference/active_record/types.rb +8 -0
- data/lib/anchor/inference/jsonapi/infer/anchor_def.rb +86 -0
- data/lib/anchor/inference/jsonapi/infer/base.rb +12 -0
- data/lib/anchor/inference/jsonapi/infer/rbs.rb +30 -0
- data/lib/anchor/inference/jsonapi/infer/relationship_references.rb +38 -0
- data/lib/anchor/inference/jsonapi/infer/resource.rb +72 -0
- data/lib/anchor/inference/jsonapi/infer/shell.rb +24 -0
- data/lib/anchor/inference/jsonapi/infer.rb +8 -0
- data/lib/anchor/inference/jsonapi/read_type.rb +47 -0
- data/lib/anchor/inference/jsonapi/types/active_record_relationships_wrapper.rb +44 -0
- data/lib/anchor/inference/jsonapi/types/anchor_comments.rb +23 -0
- data/lib/anchor/inference/jsonapi/types/base.rb +9 -0
- data/lib/anchor/inference/jsonapi/types/empty.rb +9 -0
- data/lib/anchor/inference/jsonapi/types/overridden.rb +14 -0
- data/lib/anchor/inference/jsonapi/types/readable.rb +22 -0
- data/lib/anchor/inference/jsonapi/types/relationships_wrapper.rb +29 -0
- data/lib/anchor/inference/jsonapi/types.rb +8 -0
- data/lib/anchor/json_schema/resource.rb +8 -8
- data/lib/anchor/json_schema/schema_generator.rb +1 -3
- data/lib/anchor/json_schema/serializer.rb +1 -0
- data/lib/anchor/schema.rb +1 -1
- data/lib/anchor/schema_generator.rb +1 -2
- data/lib/anchor/type_script/file_structure.rb +4 -3
- data/lib/anchor/type_script/multifile_schema_generator.rb +1 -4
- data/lib/anchor/type_script/resource.rb +17 -15
- data/lib/anchor/type_script/schema_generator.rb +1 -3
- data/lib/anchor/type_script/serializer.rb +1 -0
- data/lib/anchor/types/inference/active_record.rb +11 -35
- data/lib/anchor/types/inference/rbs.rb +35 -23
- data/lib/anchor/types.rb +123 -11
- data/lib/anchor/version.rb +1 -1
- data/lib/jsonapi-resources-anchor.rb +35 -2
- metadata +32 -4
- data/lib/anchor/resource.rb +0 -213
- data/lib/anchor/types/inference/jsonapi.rb +0 -14
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Anchor
|
|
2
|
+
module Inference
|
|
3
|
+
module JSONAPI
|
|
4
|
+
class ReadType
|
|
5
|
+
include Anchor::Typeable
|
|
6
|
+
|
|
7
|
+
attr_reader :t
|
|
8
|
+
|
|
9
|
+
delegate :convert_case, to: Anchor::Types
|
|
10
|
+
|
|
11
|
+
def initialize(klass, context: {}, include_all_fields: false)
|
|
12
|
+
@klass = klass
|
|
13
|
+
@t = Anchor::Inference::JSONAPI::Infer::Resource.infer(klass)
|
|
14
|
+
@context = context
|
|
15
|
+
@include_all_fields = include_all_fields
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.infer(...) = new(...).infer
|
|
19
|
+
|
|
20
|
+
def infer
|
|
21
|
+
id +
|
|
22
|
+
type +
|
|
23
|
+
readable(attributes).convert_case +
|
|
24
|
+
object([property(
|
|
25
|
+
"relationships",
|
|
26
|
+
readable(relationships.nullable_to_optional).convert_case,
|
|
27
|
+
)]) +
|
|
28
|
+
meta +
|
|
29
|
+
links
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def readable(t)
|
|
33
|
+
Anchor::Inference::JSONAPI::Types::Readable.new(
|
|
34
|
+
@klass, context: @context, include_all_fields: @include_all_fields
|
|
35
|
+
).wrap(t)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def id = t.pick(["id"])
|
|
39
|
+
def type = t.pick(["type"])
|
|
40
|
+
def attributes = t.pick(@klass._attributes.except(:id).keys.map(&:to_s))
|
|
41
|
+
def relationships = t.pick(@klass._relationships.keys.map(&:to_s))
|
|
42
|
+
def meta = t["meta"].type.is_a?(unknown.singleton_class) ? t.pick([]) : t.pick(["meta"])
|
|
43
|
+
def links = t["links"].type.is_a?(unknown.singleton_class) ? t.pick([]) : t.pick(["links"])
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Types
|
|
2
|
+
class ActiveRecordRelationshipsWrapper < Base
|
|
3
|
+
include Anchor::Typeable
|
|
4
|
+
|
|
5
|
+
def initialize(klass)
|
|
6
|
+
super
|
|
7
|
+
@model_klass = klass._model_class
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def wrap(t) = t.apply_higher(wrapper_type).pick(wrapper_type.keys)
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def wrapper_type
|
|
15
|
+
return @wrapper_type if defined?(@wrapper_type)
|
|
16
|
+
|
|
17
|
+
props = @klass._relationships.filter_map do |name, rel|
|
|
18
|
+
relation_name = rel.options[:relation_name]&.to_s || name.to_s
|
|
19
|
+
|
|
20
|
+
next unless (ref = @model_klass.reflections[relation_name])
|
|
21
|
+
|
|
22
|
+
# TODO: comments from DB?
|
|
23
|
+
property(name.to_s, wrapper(ref), false, nil)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@wrapper_type = object(props)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def wrapper(reflection)
|
|
30
|
+
case reflection
|
|
31
|
+
when ::ActiveRecord::Reflection::BelongsToReflection then belongs_to_type(reflection)
|
|
32
|
+
when ::ActiveRecord::Reflection::HasOneReflection then Anchor::Types::Maybe
|
|
33
|
+
when ::ActiveRecord::Reflection::HasManyReflection then Anchor::Types::Array
|
|
34
|
+
when ::ActiveRecord::Reflection::HasAndBelongsToManyReflection then Anchor::Types::Array
|
|
35
|
+
when ::ActiveRecord::Reflection::ThroughReflection then wrapper(reflection.send(:delegate_reflection))
|
|
36
|
+
else raise "#{reflection.class.name} not supported" # TODO: make this unknown wrapper somehow ?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def belongs_to_type(reflection)
|
|
41
|
+
reflection.options[:optional] ? Anchor::Types::Maybe : Anchor::Types::Identity
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Types
|
|
2
|
+
class AnchorComments < Base
|
|
3
|
+
include Anchor::Typeable
|
|
4
|
+
|
|
5
|
+
def wrap(t) = object(properties(t))
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def properties(t)
|
|
10
|
+
t.properties.map do |prop|
|
|
11
|
+
prop.dup(description: comments[prop.name] || prop.description)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def comments
|
|
16
|
+
return @comments if defined?(@comments)
|
|
17
|
+
attr_descs = @klass.try(:anchor_attributes_descriptions) || {}
|
|
18
|
+
rel_descs = @klass.try(:anchor_relationships_descriptions) || {}
|
|
19
|
+
|
|
20
|
+
@comments = attr_descs.merge(rel_descs).reject { |_, d| d.nil? }.transform_keys(&:to_s)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Types
|
|
2
|
+
class Overridden < Base
|
|
3
|
+
def wrap(t) = t.untype(names)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def names
|
|
8
|
+
count = @klass.anchor_method_added_count || Hash.new(0)
|
|
9
|
+
@klass._attributes.keys.filter_map do |name|
|
|
10
|
+
name.to_s if count[name.to_sym] > 1
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Types
|
|
2
|
+
class Readable < Base
|
|
3
|
+
def initialize(klass, context: {}, include_all_fields: false)
|
|
4
|
+
super(klass)
|
|
5
|
+
@context = context
|
|
6
|
+
@include_all_fields = include_all_fields
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def wrap(t) = t.pick(names.map(&:to_s))
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def names
|
|
14
|
+
return @klass.fields unless statically_determinable_fetchable_fields? && !@include_all_fields
|
|
15
|
+
@klass.anchor_fetchable_fields(@context)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def statically_determinable_fetchable_fields?
|
|
19
|
+
@klass.singleton_class.method_defined?(:anchor_fetchable_fields)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Types
|
|
2
|
+
class RelationshipsWrapper < Base
|
|
3
|
+
include Anchor::Typeable
|
|
4
|
+
|
|
5
|
+
def wrap(t) = t.apply_higher(wrapper_type).pick(wrapper_type.keys)
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def wrapper_type
|
|
10
|
+
return @wrapper_type if defined?(@wrapper_type)
|
|
11
|
+
props = @klass._relationships.map do |name, rel|
|
|
12
|
+
property(name.to_s, wrapper(rel), false, nil)
|
|
13
|
+
end
|
|
14
|
+
@wrapper_type = object(props)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def wrapper(relationship)
|
|
18
|
+
case relationship
|
|
19
|
+
when ::JSONAPI::Relationship::ToOne then Anchor::Types::Identity
|
|
20
|
+
when ::JSONAPI::Relationship::ToMany then Anchor::Types::Array
|
|
21
|
+
else raise "#{relationship.class.name} not supported"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def belongs_to_type(reflection)
|
|
26
|
+
reflection.options[:optional] ? Anchor::Types::Maybe : Anchor::Types::Identity
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
module Anchor::JSONSchema
|
|
2
|
-
class Resource
|
|
3
|
-
|
|
4
|
-
included_fields = schema_fetchable_fields(context:, include_all_fields:)
|
|
5
|
-
included_fields -= exclude_fields if exclude_fields
|
|
2
|
+
class Resource
|
|
3
|
+
delegate :anchor_schema_name, to: :@klass
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
def initialize(klass)
|
|
6
|
+
@klass = klass
|
|
7
|
+
end
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
def express(context: {}, include_all_fields:)
|
|
10
|
+
t = Anchor::Inference::JSONAPI::ReadType.infer(@klass, context:, include_all_fields:).omit(["meta", "links"])
|
|
11
|
+
t["relationships"].type.properties.count > 0 ? t : t.omit(["relationships"])
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -2,11 +2,10 @@ module Anchor::JSONSchema
|
|
|
2
2
|
class SchemaGenerator < Anchor::SchemaGenerator
|
|
3
3
|
delegate :type_property, to: Anchor::JSONSchema::Serializer
|
|
4
4
|
|
|
5
|
-
def initialize(register:, context: {}, include_all_fields: false
|
|
5
|
+
def initialize(register:, context: {}, include_all_fields: false) # rubocop:disable Lint/MissingSuper
|
|
6
6
|
@register = register
|
|
7
7
|
@context = context
|
|
8
8
|
@include_all_fields = include_all_fields
|
|
9
|
-
@exclude_fields = exclude_fields
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
def call
|
|
@@ -41,7 +40,6 @@ module Anchor::JSONSchema
|
|
|
41
40
|
resource.anchor_schema_name => type_property(resource.express(
|
|
42
41
|
context: @context,
|
|
43
42
|
include_all_fields: @include_all_fields,
|
|
44
|
-
exclude_fields: @exclude_fields.nil? ? [] : @exclude_fields[r.anchor_schema_name.to_sym],
|
|
45
43
|
)),
|
|
46
44
|
}
|
|
47
45
|
end.reduce(&:merge)
|
|
@@ -20,6 +20,7 @@ module Anchor::JSONSchema
|
|
|
20
20
|
when Anchor::Types::Reference then { "$ref" => "#/$defs/#{type.name}" }
|
|
21
21
|
when Anchor::Types::Object, Anchor::Types::Object.singleton_class then serialize_object(type)
|
|
22
22
|
when Anchor::Types::Enum.singleton_class then { enum: type.values.map(&:second) }
|
|
23
|
+
when Anchor::Types::Identity then type_property(type.type)
|
|
23
24
|
when Anchor::Types::Unknown.singleton_class then {}
|
|
24
25
|
when Anchor::Types::Intersection then {}
|
|
25
26
|
else raise RuntimeError
|
data/lib/anchor/schema.rb
CHANGED
|
@@ -3,7 +3,7 @@ module Anchor
|
|
|
3
3
|
class DuplicateTypeError < StandardError; end
|
|
4
4
|
|
|
5
5
|
class << self
|
|
6
|
-
Register =
|
|
6
|
+
Register = Data.define(:resources, :enums)
|
|
7
7
|
|
|
8
8
|
def register
|
|
9
9
|
Register.new(resources: @resources || [], enums: @enums || [])
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
module Anchor
|
|
2
2
|
class SchemaGenerator
|
|
3
|
-
def initialize(register:, context:, include_all_fields
|
|
3
|
+
def initialize(register:, context:, include_all_fields:)
|
|
4
4
|
@register = register
|
|
5
5
|
@context = context
|
|
6
6
|
@include_all_fields = include_all_fields
|
|
7
|
-
@exclude_fields = exclude_fields
|
|
8
7
|
end
|
|
9
8
|
|
|
10
9
|
def self.call(...)
|
|
@@ -2,7 +2,7 @@ module Anchor::TypeScript
|
|
|
2
2
|
class FileStructure
|
|
3
3
|
# @param file_name [String] name of file, e.g. model.ts
|
|
4
4
|
# @param type [Anchor::Types]
|
|
5
|
-
Import =
|
|
5
|
+
Import = Data.define(:file_name, :type)
|
|
6
6
|
class FileUtils
|
|
7
7
|
def self.imports_to_code(imports)
|
|
8
8
|
imports.group_by(&:file_name).map do |file_name, file_imports|
|
|
@@ -71,16 +71,17 @@ module Anchor::TypeScript
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def relationships_to_import
|
|
74
|
-
relationships = @object.properties.find { |p| p.name == :relationships }
|
|
74
|
+
relationships = @object.properties.find { |p| p.name.to_sym == :relationships }
|
|
75
75
|
return [] if relationships.nil? || relationships.type.try(:properties).nil?
|
|
76
76
|
relationships.type.properties.flat_map { |p| references_from_type(p.type) }.uniq.sort_by(&:anchor_schema_name)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def references_from_type(type)
|
|
80
80
|
case type
|
|
81
|
-
when Anchor::Types::Array, Anchor::Types::Maybe then references_from_type(type.type)
|
|
81
|
+
when Anchor::Types::Array, Anchor::Types::Maybe, Anchor::Types::Identity then references_from_type(type.type)
|
|
82
82
|
when Anchor::Types::Union then type.types.flat_map { |t| references_from_type(t) }
|
|
83
83
|
when Anchor::Types::Reference then [type]
|
|
84
|
+
when Anchor::Types::Unknown.singleton_class then []
|
|
84
85
|
end.uniq.sort_by(&:anchor_schema_name)
|
|
85
86
|
end
|
|
86
87
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Anchor::TypeScript
|
|
2
2
|
class MultifileSchemaGenerator < Anchor::SchemaGenerator
|
|
3
|
-
Result =
|
|
3
|
+
Result = Data.define(:name, :text, :type)
|
|
4
4
|
|
|
5
5
|
module FileType
|
|
6
6
|
RESOURCE = "resource"
|
|
@@ -11,14 +11,12 @@ module Anchor::TypeScript
|
|
|
11
11
|
register:,
|
|
12
12
|
context: {},
|
|
13
13
|
include_all_fields: false,
|
|
14
|
-
exclude_fields: nil,
|
|
15
14
|
manually_editable: true,
|
|
16
15
|
resource_file_extension: ".ts"
|
|
17
16
|
)
|
|
18
17
|
@register = register
|
|
19
18
|
@context = context
|
|
20
19
|
@include_all_fields = include_all_fields
|
|
21
|
-
@exclude_fields = exclude_fields
|
|
22
20
|
@manually_editable = manually_editable
|
|
23
21
|
@resource_file_extension = "." + resource_file_extension.sub(/^\./, "")
|
|
24
22
|
end
|
|
@@ -47,7 +45,6 @@ module Anchor::TypeScript
|
|
|
47
45
|
definition = r.definition(
|
|
48
46
|
context: @context,
|
|
49
47
|
include_all_fields: @include_all_fields,
|
|
50
|
-
exclude_fields: @exclude_fields.nil? ? [] : @exclude_fields[r.anchor_schema_name.to_sym],
|
|
51
48
|
)
|
|
52
49
|
|
|
53
50
|
file_structure = ::Anchor::TypeScript::FileStructure.new(definition, extension: @resource_file_extension)
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
module Anchor::TypeScript
|
|
2
|
-
class Resource
|
|
3
|
-
Definition =
|
|
2
|
+
class Resource
|
|
3
|
+
Definition = Data.define(:name, :object)
|
|
4
|
+
|
|
5
|
+
delegate :anchor_schema_name, to: :@klass
|
|
6
|
+
|
|
7
|
+
def initialize(klass)
|
|
8
|
+
@klass = klass
|
|
9
|
+
end
|
|
4
10
|
|
|
5
11
|
def express(...)
|
|
6
12
|
@object = object(...)
|
|
@@ -13,21 +19,17 @@ module Anchor::TypeScript
|
|
|
13
19
|
Definition.new(name: anchor_schema_name, object: @object)
|
|
14
20
|
end
|
|
15
21
|
|
|
16
|
-
def object(context: {}, include_all_fields
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
def object(context: {}, include_all_fields:)
|
|
23
|
+
t = Anchor::Inference::JSONAPI::ReadType.infer(@klass, context:, include_all_fields:)
|
|
24
|
+
return t if t["relationships"].type.properties.count > 0
|
|
25
|
+
return t.omit(["relationships"]) unless Anchor.config.empty_relationship_type
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
relationships_property = Anchor::Types::Property.new(:relationships, Anchor.config.empty_relationship_type.call)
|
|
23
|
-
end
|
|
27
|
+
t.overwrite(Anchor::Types::Object.new([empty_relationships_property]))
|
|
28
|
+
end
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
Array.wrap(anchor_attributes_properties(included_fields:)) +
|
|
27
|
-
Array.wrap(relationships_property) +
|
|
28
|
-
[anchor_meta_property].compact + [anchor_links_property].compact
|
|
30
|
+
private
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
def empty_relationships_property = property("relationships", Anchor.config.empty_relationship_type.call)
|
|
33
|
+
def property(...) = Anchor::Types::Property.new(...)
|
|
32
34
|
end
|
|
33
35
|
end
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
module Anchor::TypeScript
|
|
2
2
|
class SchemaGenerator < Anchor::SchemaGenerator
|
|
3
|
-
def initialize(register:, context: {}, include_all_fields: false
|
|
3
|
+
def initialize(register:, context: {}, include_all_fields: false) # rubocop:disable Lint/MissingSuper
|
|
4
4
|
@register = register
|
|
5
5
|
@context = context
|
|
6
6
|
@include_all_fields = include_all_fields
|
|
7
|
-
@exclude_fields = exclude_fields
|
|
8
7
|
end
|
|
9
8
|
|
|
10
9
|
def call
|
|
@@ -15,7 +14,6 @@ module Anchor::TypeScript
|
|
|
15
14
|
r.express(
|
|
16
15
|
context: @context,
|
|
17
16
|
include_all_fields: @include_all_fields,
|
|
18
|
-
exclude_fields: @exclude_fields.nil? ? [] : @exclude_fields[r.anchor_schema_name.to_sym],
|
|
19
17
|
)
|
|
20
18
|
end
|
|
21
19
|
|
|
@@ -18,6 +18,7 @@ module Anchor::TypeScript
|
|
|
18
18
|
when Anchor::Types::Reference then type.name
|
|
19
19
|
when Anchor::Types::Object, Anchor::Types::Object.singleton_class then serialize_object(type, depth)
|
|
20
20
|
when Anchor::Types::Enum.singleton_class then type.anchor_schema_name
|
|
21
|
+
when Anchor::Types::Identity then type_string(type.type)
|
|
21
22
|
when Anchor::Types::Unknown.singleton_class then "unknown"
|
|
22
23
|
else raise RuntimeError
|
|
23
24
|
end
|
|
@@ -1,49 +1,26 @@
|
|
|
1
1
|
module Anchor::Types::Inference
|
|
2
2
|
module ActiveRecord
|
|
3
|
-
class << self
|
|
4
|
-
# @return [Proc{Type => Type, Anchor::Types::Maybe<Type>, Anchor::Types::Array<Type>}]
|
|
5
|
-
def wrapper_from_reflection(reflection)
|
|
6
|
-
case reflection
|
|
7
|
-
when ::ActiveRecord::Reflection::BelongsToReflection then ->(type) { belongs_to_type(reflection, type) }
|
|
8
|
-
when ::ActiveRecord::Reflection::HasOneReflection then ->(type) { Anchor::Types::Maybe.new(type) }
|
|
9
|
-
when ::ActiveRecord::Reflection::HasManyReflection then ->(type) { Anchor::Types::Array.new(type) }
|
|
10
|
-
when ::ActiveRecord::Reflection::HasAndBelongsToManyReflection then ->(type) { Anchor::Types::Array.new(type) }
|
|
11
|
-
when ::ActiveRecord::Reflection::ThroughReflection then wrapper_from_reflection(reflection.send(:delegate_reflection))
|
|
12
|
-
else raise "#{reflection.class.name} not supported"
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
# @param reflection [::ActiveRecord::Reflection::BelongsToReflection]
|
|
19
|
-
# @param type [Anchor::Types]
|
|
20
|
-
# @return [Anchor::Types::Maybe<Type>, Type]
|
|
21
|
-
def belongs_to_type(reflection, type)
|
|
22
|
-
reflection.options[:optional] ? Anchor::Types::Maybe.new(type) : type
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
3
|
module SQL
|
|
27
4
|
class << self
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
5
|
+
def default_ar_column_to_type(column)
|
|
6
|
+
metadata_type = from_sql_type_metadata(column.sql_type_metadata)
|
|
7
|
+
column_type = from_column_type(column.type)
|
|
31
8
|
|
|
32
|
-
|
|
33
|
-
type = Anchor::Types::Array.new(Anchor::Types::String)
|
|
34
|
-
end
|
|
9
|
+
type = [metadata_type, column_type, Anchor::Types::Unknown].compact.first
|
|
35
10
|
|
|
36
11
|
column.null ? Anchor::Types::Maybe.new(type) : type
|
|
37
12
|
end
|
|
38
13
|
|
|
39
|
-
def default_ar_column_to_type(column)
|
|
40
|
-
from(column, check_config: false)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
14
|
private
|
|
44
15
|
|
|
16
|
+
def from_sql_type_metadata(sql_type_metadata)
|
|
17
|
+
case sql_type_metadata.sql_type
|
|
18
|
+
when "character varying[]", "text[]" then Anchor::Types::Array.new(Anchor::Types::String)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
45
22
|
# inspiration from https://github.com/ElMassimo/types_from_serializers/blob/146ba40bc1a0da37473cd3b705a8ca982c2d173f/types_from_serializers/lib/types_from_serializers/generator.rb#L382
|
|
46
|
-
def
|
|
23
|
+
def from_column_type(type)
|
|
47
24
|
case type
|
|
48
25
|
when :boolean then Anchor::Types::Boolean
|
|
49
26
|
when :date then Anchor::Types::String
|
|
@@ -57,7 +34,6 @@ module Anchor::Types::Inference
|
|
|
57
34
|
when :text then Anchor::Types::String
|
|
58
35
|
when :time then Anchor::Types::String
|
|
59
36
|
when :uuid then Anchor::Types::String
|
|
60
|
-
else Anchor::Types::Unknown
|
|
61
37
|
end
|
|
62
38
|
end
|
|
63
39
|
end
|
|
@@ -1,33 +1,29 @@
|
|
|
1
1
|
module Anchor::Types::Inference
|
|
2
2
|
module RBS
|
|
3
3
|
class << self
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@loader ||= ::RBS::EnvironmentLoader.new.tap do |l|
|
|
8
|
-
l.add(path: Rails.root.join("sig"))
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
@env ||= ::RBS::Environment.from_loader(@loader).resolve_type_names
|
|
12
|
-
|
|
13
|
-
# TODO: Do we need both the absolute and non-absolute namespaces?
|
|
14
|
-
klass = @env.class_decls.keys.find { |kl| [class_name.to_s, "::#{class_name}"].include?(kl.to_s) }
|
|
15
|
-
return Types::Unknown unless klass
|
|
16
|
-
|
|
17
|
-
@builder ||= ::RBS::DefinitionBuilder.new(env: @env)
|
|
18
|
-
instance = @builder.build_instance(klass)
|
|
19
|
-
|
|
20
|
-
instance_method = instance.methods[method_name]
|
|
21
|
-
return Types::Unknown unless instance_method
|
|
4
|
+
def enabled?
|
|
5
|
+
Anchor.config.rbs.to_s == "fallback"
|
|
6
|
+
end
|
|
22
7
|
|
|
23
|
-
|
|
24
|
-
return
|
|
8
|
+
def validate!
|
|
9
|
+
return if defined?(::RBS) && ::RBS::VERSION.first == "3"
|
|
10
|
+
raise "RBS version conflict: rbs ~> 3 required."
|
|
11
|
+
end
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
# @param klass [Class]
|
|
14
|
+
# @return [Class, nil]
|
|
15
|
+
def get_definition(klass)
|
|
16
|
+
env.class_decls.keys.find do |definition|
|
|
17
|
+
# TODO: Do we need both absolute and relative here?
|
|
18
|
+
[klass.name, "::#{klass.name}"].include?(definition.to_s)
|
|
19
|
+
end
|
|
28
20
|
end
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
def build_instance(klass)
|
|
23
|
+
if (definition = get_definition(klass))
|
|
24
|
+
builder.build_instance(definition)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
31
27
|
|
|
32
28
|
def from_rbs_type(type)
|
|
33
29
|
case type
|
|
@@ -46,6 +42,22 @@ module Anchor::Types::Inference
|
|
|
46
42
|
end
|
|
47
43
|
end
|
|
48
44
|
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def builder
|
|
48
|
+
@builder ||= ::RBS::DefinitionBuilder.new(env:)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def env
|
|
52
|
+
@env ||= ::RBS::Environment.from_loader(loader).resolve_type_names
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def loader
|
|
56
|
+
@loader ||= ::RBS::EnvironmentLoader.new.tap do |l|
|
|
57
|
+
l.add(path: Anchor.config.rbs_sig_path)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
49
61
|
def from_record(type)
|
|
50
62
|
properties = type.fields.map do |name, type|
|
|
51
63
|
Types::Property.new(name, from_rbs_type(type))
|