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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5ebb4d75fd276f4cddd84e0da7e2e370e0648b735eb47aded47f2b90b9432196
|
|
4
|
+
data.tar.gz: caeb16f3b03cad154bc2a0d3f979d68016b20c932e9aef866c257a612d55c79d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 51c613570c3beb95ad4ba0eb53196ea3a01cd14bcfb02dae8b355b4b173c4fea14e6effb88e5501d7d7eb9355f769d75b2c98271fa7dc64b103025d0fb4792ad
|
|
7
|
+
data.tar.gz: 6322f776f08e23cdb782e1519341d3d5ba5494c980444c761169e381dc691e4cd8a1395116db748ce8c45dbabc8f917488b2eb5c35db382ee8193a3e64516c77
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Anchor
|
|
2
|
+
module Typeable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
def object(...) = Anchor::Types::Object.new(...)
|
|
7
|
+
def property(...) = Anchor::Types::Property.new(...)
|
|
8
|
+
def maybe(...) = Anchor::Types::Maybe.new(...)
|
|
9
|
+
def array(...) = Anchor::Types::Array.new(...)
|
|
10
|
+
def union(...) = Anchor::Types::Union.new(...)
|
|
11
|
+
def literal(...) = Anchor::Types::Literal.new(...)
|
|
12
|
+
def literals(values) = union(values.map { |value| literal(value) })
|
|
13
|
+
def reference(...) = Anchor::Types::Reference.new(...)
|
|
14
|
+
def references(names) = union(names.map { |name| reference(name) })
|
|
15
|
+
def record(value_type = Anchor::Types::Unknown) = Anchor::Types::Record.new(value_type)
|
|
16
|
+
|
|
17
|
+
def boolean = Anchor::Types::Boolean
|
|
18
|
+
def null = Anchor::Types::Null
|
|
19
|
+
def unknown = Anchor::Types::Unknown
|
|
20
|
+
def string = Anchor::Types::String
|
|
21
|
+
def float = Anchor::Types::Float
|
|
22
|
+
def integer = Anchor::Types::Integer
|
|
23
|
+
def big_decimal = Anchor::Types::BigDecimal
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/anchor/config.rb
CHANGED
|
@@ -11,7 +11,9 @@ module Anchor
|
|
|
11
11
|
:array_bracket_notation,
|
|
12
12
|
:infer_default_as_non_null,
|
|
13
13
|
:ar_comment_to_string,
|
|
14
|
-
:infer_ar_enums
|
|
14
|
+
:infer_ar_enums,
|
|
15
|
+
:rbs,
|
|
16
|
+
:rbs_sig_path
|
|
15
17
|
|
|
16
18
|
def initialize
|
|
17
19
|
@ar_column_to_type = nil
|
|
@@ -26,6 +28,8 @@ module Anchor
|
|
|
26
28
|
@infer_default_as_non_null = nil
|
|
27
29
|
@ar_comment_to_string = nil
|
|
28
30
|
@infer_ar_enums = nil
|
|
31
|
+
@rbs = "off"
|
|
32
|
+
@rbs_sig_path = Rails.root.join("rbs")
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
35
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Anchor::Inference::ActiveRecord::Infer
|
|
2
|
+
class Columns < Base
|
|
3
|
+
def infer = object(properties)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def properties
|
|
8
|
+
@klass.columns_hash.map do |name, column|
|
|
9
|
+
next property(name, Anchor.config.ar_column_to_type.call(column)) if Anchor.config.ar_column_to_type
|
|
10
|
+
metadata_type = from_sql_type_metadata(column.sql_type_metadata)
|
|
11
|
+
column_type = from_column_type(column.type)
|
|
12
|
+
|
|
13
|
+
type = [metadata_type, column_type, unknown].compact.first
|
|
14
|
+
type = column.null ? maybe(type) : type
|
|
15
|
+
property(name, type)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def from_sql_type_metadata(sql_type_metadata)
|
|
20
|
+
case sql_type_metadata.sql_type
|
|
21
|
+
when "character varying[]", "text[]" then array(string)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def from_column_type(type)
|
|
26
|
+
case type
|
|
27
|
+
when :boolean then boolean
|
|
28
|
+
when :date then string
|
|
29
|
+
when :datetime then string
|
|
30
|
+
when :decimal then big_decimal
|
|
31
|
+
when :float then float
|
|
32
|
+
when :integer then integer
|
|
33
|
+
when :json then record
|
|
34
|
+
when :jsonb then record
|
|
35
|
+
when :string then string
|
|
36
|
+
when :text then string
|
|
37
|
+
when :time then string
|
|
38
|
+
when :uuid then string
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Anchor::Inference::ActiveRecord::Infer
|
|
2
|
+
class Enums < Base
|
|
3
|
+
def infer = object(properties)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def properties
|
|
8
|
+
@klass.columns_hash.slice(*defined_enums.keys).merge(defined_enums) do |name, column, enum|
|
|
9
|
+
property(name, column.null ? maybe(enum) : enum)
|
|
10
|
+
end.values
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def defined_enums
|
|
14
|
+
@defined_enums ||= @klass.defined_enums.transform_values { |enum| literals(enum.values) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# TODO: Is attribute_types.keys ⊅ columns_hash.keys possible?
|
|
2
|
+
# def superset?(klass) = klass.attribute_types.keys.to_set.superset?(klass.columns_hash.keys.to_set)
|
|
3
|
+
# !ActiveRecord::Base.descendants.reject(&:abstract_class?).all? { |k| superset?(k) }
|
|
4
|
+
module Anchor::Inference::ActiveRecord::Infer
|
|
5
|
+
class Model < Base
|
|
6
|
+
module T
|
|
7
|
+
include Anchor::Inference::ActiveRecord::Types
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def infer
|
|
11
|
+
res = [serialized, overridden, presence_required, defaulted, column_comments].compact.reduce(columns) do |acc, elem|
|
|
12
|
+
elem.wrap(acc)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
res.overwrite(
|
|
16
|
+
rbs.pick(
|
|
17
|
+
res.pick_by_value(unknown.singleton_class).keys,
|
|
18
|
+
),
|
|
19
|
+
keep_description: :left,
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def columns
|
|
26
|
+
Columns.infer(@klass).overwrite(enums, keep_description: :left)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def enums
|
|
30
|
+
return object([]) unless Anchor.config.infer_ar_enums
|
|
31
|
+
@enum_types ||= Enums.infer(@klass)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def column_comments
|
|
35
|
+
return unless Anchor.config.use_active_record_comment
|
|
36
|
+
T::ColumnComments.new(@klass)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def rbs
|
|
40
|
+
return @rbs if defined?(@rbs)
|
|
41
|
+
return object([]) unless Anchor::Types::Inference::RBS.enabled?
|
|
42
|
+
Anchor::Types::Inference::RBS.validate!
|
|
43
|
+
@rbs = RBS.infer(@klass)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def serialized
|
|
47
|
+
T::Serialized.new(@klass)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def overridden
|
|
51
|
+
T::Overridden.new(@klass)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def presence_required
|
|
55
|
+
return unless Anchor.config.use_active_record_validations
|
|
56
|
+
T::PresenceRequired.new(@klass)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def defaulted
|
|
60
|
+
return unless Anchor.config.infer_default_as_non_null
|
|
61
|
+
T::Defaulted.new(@klass)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Anchor::Inference::ActiveRecord::Infer
|
|
2
|
+
class RBS < Base
|
|
3
|
+
def infer = object(properties)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def properties
|
|
8
|
+
included_attributes.map do |method_name|
|
|
9
|
+
type = rbs.from_rbs_type(instance.methods[method_name].method_types.first.type.return_type)
|
|
10
|
+
Anchor::Types::Property.new(method_name.to_s, type)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def included_attributes
|
|
15
|
+
instance.methods.filter_map do |method_name, method_def|
|
|
16
|
+
next if method_def&.method_types&.length != 1
|
|
17
|
+
method_name
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def instance
|
|
22
|
+
return @instance if defined?(@instance)
|
|
23
|
+
@instance ||= rbs.build_instance(@klass)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rbs
|
|
27
|
+
@rbs ||= Anchor::Types::Inference::RBS
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Anchor::Inference::ActiveRecord::Types
|
|
2
|
+
class ColumnComments < Base
|
|
3
|
+
include Anchor::Typeable
|
|
4
|
+
|
|
5
|
+
def wrap(t) = object(add_comments(t))
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def add_comments(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
|
+
@comments ||= @klass.columns_hash.filter_map do |name, column|
|
|
17
|
+
next unless column.comment
|
|
18
|
+
description = serialize_comment(column.comment)
|
|
19
|
+
[name, description]
|
|
20
|
+
end.to_h
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serialize_comment(comment)
|
|
24
|
+
return comment unless Anchor.config.ar_comment_to_string
|
|
25
|
+
|
|
26
|
+
Anchor.config.ar_comment_to_string.call(comment)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Anchor::Inference::ActiveRecord::Types
|
|
2
|
+
class Defaulted < Base
|
|
3
|
+
def wrap(t) = t.pick(names).nonnullable + t.omit(names)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def names
|
|
8
|
+
@klass.columns_hash.filter_map do |name, column|
|
|
9
|
+
name if has_default?(column)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def has_default?(column)
|
|
14
|
+
column.default.present? || column.default_function.present? && column.instance_variable_get(:@generated).blank?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Anchor::Inference::ActiveRecord::Types
|
|
2
|
+
class Overridden < Base
|
|
3
|
+
def wrap(t) = t.untype(names)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def names
|
|
8
|
+
@klass.attribute_types.keys.filter do |name|
|
|
9
|
+
next unless @klass.method_defined?(name.to_sym)
|
|
10
|
+
expected_generators.none? do |generator|
|
|
11
|
+
@klass.instance_method(name.to_sym).owner.is_a?(generator)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def expected_generators
|
|
17
|
+
@expected_generators ||= [
|
|
18
|
+
ActiveRecord::AttributeMethods::PrimaryKey,
|
|
19
|
+
ActiveRecord::AttributeMethods::GeneratedAttributeMethods,
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Anchor::Inference::ActiveRecord::Types
|
|
2
|
+
class PresenceRequired < Base
|
|
3
|
+
def wrap(t) = t.pick(names).nonnullable + t.omit(names)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def names
|
|
8
|
+
@klass.attribute_types.keys.filter do |name|
|
|
9
|
+
presence_required_for?(name)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def presence_required_for?(attribute)
|
|
14
|
+
@klass.validators_on(attribute).any? do |validator|
|
|
15
|
+
case validator
|
|
16
|
+
when ActiveRecord::Validations::NumericalityValidator then numericality_presence_required?(validator)
|
|
17
|
+
when ActiveRecord::Validations::PresenceValidator then presence_required?(validator)
|
|
18
|
+
else false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def numericality_presence_required?(validator)
|
|
24
|
+
opts = validator.options.with_indifferent_access
|
|
25
|
+
!(opts[:allow_nil] || opts[:if] || opts[:unless] || opts[:on])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def presence_required?(validator)
|
|
29
|
+
opts = validator.options.with_indifferent_access
|
|
30
|
+
!(opts[:if] || opts[:unless] || opts[:on])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Infer
|
|
2
|
+
class AnchorDef < Base
|
|
3
|
+
delegate :resource_key_type, :_type, :_attributes, :_relationships, to: :@klass
|
|
4
|
+
|
|
5
|
+
def initialize(klass)
|
|
6
|
+
super(klass)
|
|
7
|
+
@anchor_attributes = klass.try(:anchor_attributes) || {}
|
|
8
|
+
@anchor_relationships = klass.try(:anchor_relationships) || {}
|
|
9
|
+
@anchor_attributes_descriptions = klass.try(:anchor_attributes_descriptions) || {}
|
|
10
|
+
@anchor_relationships_descriptions = klass.try(:anchor_relationships_descriptions) || {}
|
|
11
|
+
@anchor_links_schema = klass.try(:anchor_links_schema) || nil
|
|
12
|
+
@anchor_meta_schema = klass.try(:anchor_meta_schema) || nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def infer
|
|
16
|
+
object([
|
|
17
|
+
id,
|
|
18
|
+
type,
|
|
19
|
+
*attributes,
|
|
20
|
+
*relationships,
|
|
21
|
+
meta,
|
|
22
|
+
links,
|
|
23
|
+
].compact)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def attributes
|
|
29
|
+
_attributes.except(:id).filter_map do |attr, _|
|
|
30
|
+
next unless @anchor_attributes.key?(attr)
|
|
31
|
+
|
|
32
|
+
property(
|
|
33
|
+
attr.to_s,
|
|
34
|
+
@anchor_attributes[attr],
|
|
35
|
+
false,
|
|
36
|
+
@anchor_attributes_descriptions[attr],
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def relationships
|
|
42
|
+
_relationships.filter_map do |name, rel|
|
|
43
|
+
next if @anchor_relationships.exclude?(name)
|
|
44
|
+
|
|
45
|
+
anchor_relationship = @anchor_relationships[name]
|
|
46
|
+
polymorphic = anchor_relationship.resources.present?
|
|
47
|
+
|
|
48
|
+
base_type = if polymorphic
|
|
49
|
+
references(anchor_relationship.resources.map(&:anchor_schema_name))
|
|
50
|
+
else
|
|
51
|
+
reference(anchor_relationship.resource.anchor_schema_name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if rel.is_a?(::JSONAPI::Relationship::ToMany)
|
|
55
|
+
null_elements = anchor_relationship.null_elements.present?
|
|
56
|
+
base_type |= null if null_elements
|
|
57
|
+
base_type = array(base_type)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
type = anchor_relationship.null.present? ? maybe(base_type) : base_type
|
|
61
|
+
property(name.to_s, type, false, @anchor_relationships_descriptions[name])
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def id
|
|
66
|
+
# TODO: resource_key_type can also return a proc
|
|
67
|
+
res_key_type = case resource_key_type
|
|
68
|
+
when :integer then integer
|
|
69
|
+
else string
|
|
70
|
+
end
|
|
71
|
+
property("id", res_key_type)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def type = property("type", literal(_type))
|
|
75
|
+
|
|
76
|
+
def links
|
|
77
|
+
return unless @anchor_links_schema
|
|
78
|
+
property("links", @anchor_links_schema)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def meta
|
|
82
|
+
return unless @anchor_meta_schema
|
|
83
|
+
property("meta", @anchor_meta_schema)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Infer
|
|
2
|
+
class RBS < Base
|
|
3
|
+
def infer = object(properties)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def properties
|
|
8
|
+
included_attributes.map do |method_name|
|
|
9
|
+
type = rbs.from_rbs_type(instance.methods[method_name].method_types.first.type.return_type)
|
|
10
|
+
Anchor::Types::Property.new(method_name.to_s, type)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def included_attributes
|
|
15
|
+
instance.methods.filter_map do |method_name, method_def|
|
|
16
|
+
next if method_def&.method_types&.length != 1
|
|
17
|
+
method_name
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def instance
|
|
22
|
+
return @instance if defined?(@instance)
|
|
23
|
+
@instance ||= rbs.build_instance(@klass)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rbs
|
|
27
|
+
@rbs ||= Anchor::Types::Inference::RBS
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Infer
|
|
2
|
+
class RelationshipReferences < Base
|
|
3
|
+
def infer = object(properties)
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def properties
|
|
8
|
+
relationships.map { |name, rel| property(name.to_s, type_for(rel)) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def type_for(rel)
|
|
12
|
+
begin
|
|
13
|
+
rel.resource_klass
|
|
14
|
+
rescue NameError => e
|
|
15
|
+
Rails.logger.warn(e.message)
|
|
16
|
+
return unknown
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
return reference(rel.resource_klass.anchor_schema_name) unless rel.polymorphic?
|
|
20
|
+
|
|
21
|
+
version = nil
|
|
22
|
+
version ||= rel.respond_to?(:polymorphic_types) && :new # 0.11.0.beta2
|
|
23
|
+
version ||= rel.class.respond_to?(:polymorphic_types) && :old # TODO: < 0.11.0.beta2
|
|
24
|
+
|
|
25
|
+
polymorphic_types = case version
|
|
26
|
+
when :new then rel.polymorphic_types
|
|
27
|
+
when :old then rel.class.polymorphic_types
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return reference(rel.resource_klass.anchor_schema_name) unless polymorphic_types
|
|
31
|
+
|
|
32
|
+
resource_klasses = polymorphic_types.map { |t| @klass.resource_klass_for(t) }
|
|
33
|
+
union(resource_klasses.map { |rk| reference(rk.anchor_schema_name) })
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def relationships = @klass._relationships
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Infer
|
|
2
|
+
module T
|
|
3
|
+
include Anchor::Inference::JSONAPI::Types
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class Resource < Base
|
|
7
|
+
def infer
|
|
8
|
+
shell = Shell.infer(@klass)
|
|
9
|
+
annotated = AnchorDef.infer(@klass)
|
|
10
|
+
|
|
11
|
+
model = delegated_attrs(attributes) + attributes + relationships
|
|
12
|
+
inferred = (model + shell).pick(shell.keys)
|
|
13
|
+
|
|
14
|
+
fallback = rbs.pick(inferred.pick_by_value(unknown.singleton_class).keys)
|
|
15
|
+
result = annotated + inferred.overwrite(fallback, keep_description: :left)
|
|
16
|
+
|
|
17
|
+
anchor_comments.wrap(result)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def attributes
|
|
23
|
+
@attributes ||= overridden.wrap(active_record_model)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def relationships
|
|
27
|
+
base_relationships = RelationshipReferences.infer(@klass)
|
|
28
|
+
|
|
29
|
+
jsonapi_relationships = relationships_wrapper.wrap(base_relationships)
|
|
30
|
+
active_record_relationships = active_record_relationships_wrapper.wrap(base_relationships)
|
|
31
|
+
|
|
32
|
+
active_record_relationships + jsonapi_relationships
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def rbs
|
|
36
|
+
return @rbs if defined?(@rbs)
|
|
37
|
+
return object([]) unless Anchor::Types::Inference::RBS.enabled?
|
|
38
|
+
Anchor::Types::Inference::RBS.validate!
|
|
39
|
+
@rbs = RBS.infer(@klass)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def active_record_model
|
|
43
|
+
return object([]) unless @klass._model_class < ActiveRecord::Base
|
|
44
|
+
Anchor::Inference::ActiveRecord::Infer::Model.infer(@klass._model_class)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def active_record_relationships_wrapper
|
|
48
|
+
return T::Empty.new unless @klass._model_class < ActiveRecord::Base
|
|
49
|
+
T::ActiveRecordRelationshipsWrapper.new(@klass)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def relationships_wrapper
|
|
53
|
+
T::RelationshipsWrapper.new(@klass)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def overridden
|
|
57
|
+
T::Overridden.new(@klass)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def anchor_comments
|
|
61
|
+
T::AnchorComments.new(@klass)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def delegated_attrs(attrs)
|
|
65
|
+
props = @klass._attributes.filter_map do |name, opts|
|
|
66
|
+
next unless (delegate = opts[:delegate]&.to_s)
|
|
67
|
+
attrs[delegate]&.dup(name: name.to_s) || property(name.to_s, unknown)
|
|
68
|
+
end
|
|
69
|
+
object(props)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Anchor::Inference::JSONAPI::Infer
|
|
2
|
+
class Shell < Base
|
|
3
|
+
def infer
|
|
4
|
+
object({
|
|
5
|
+
id: unknown,
|
|
6
|
+
type: unknown,
|
|
7
|
+
**attributes.index_with { unknown },
|
|
8
|
+
**relationships.index_with { unknown },
|
|
9
|
+
meta: unknown,
|
|
10
|
+
links: unknown,
|
|
11
|
+
})
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def object(hash)
|
|
17
|
+
props = hash.map { |key, type| property(key.to_s, type) }
|
|
18
|
+
Anchor::Types::Object.new(props)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def attributes = @klass._attributes.except(:id).keys
|
|
22
|
+
def relationships = @klass._relationships.keys
|
|
23
|
+
end
|
|
24
|
+
end
|