jsonapi-resources-anchor 2.12.0 → 2.13.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/anchor/concerns/typeable.rb +26 -0
  3. data/lib/anchor/config.rb +5 -1
  4. data/lib/anchor/inference/active_record/infer/base.rb +12 -0
  5. data/lib/anchor/inference/active_record/infer/columns.rb +42 -0
  6. data/lib/anchor/inference/active_record/infer/enums.rb +17 -0
  7. data/lib/anchor/inference/active_record/infer/model.rb +64 -0
  8. data/lib/anchor/inference/active_record/infer/rbs.rb +30 -0
  9. data/lib/anchor/inference/active_record/infer.rb +8 -0
  10. data/lib/anchor/inference/active_record/types/base.rb +9 -0
  11. data/lib/anchor/inference/active_record/types/column_comments.rb +29 -0
  12. data/lib/anchor/inference/active_record/types/defaulted.rb +17 -0
  13. data/lib/anchor/inference/active_record/types/overridden.rb +23 -0
  14. data/lib/anchor/inference/active_record/types/presence_required.rb +33 -0
  15. data/lib/anchor/inference/active_record/types/serialized.rb +13 -0
  16. data/lib/anchor/inference/active_record/types.rb +8 -0
  17. data/lib/anchor/inference/jsonapi/infer/anchor_def.rb +86 -0
  18. data/lib/anchor/inference/jsonapi/infer/base.rb +12 -0
  19. data/lib/anchor/inference/jsonapi/infer/rbs.rb +30 -0
  20. data/lib/anchor/inference/jsonapi/infer/relationship_references.rb +38 -0
  21. data/lib/anchor/inference/jsonapi/infer/resource.rb +72 -0
  22. data/lib/anchor/inference/jsonapi/infer/shell.rb +24 -0
  23. data/lib/anchor/inference/jsonapi/infer.rb +8 -0
  24. data/lib/anchor/inference/jsonapi/read_type.rb +47 -0
  25. data/lib/anchor/inference/jsonapi/types/active_record_relationships_wrapper.rb +44 -0
  26. data/lib/anchor/inference/jsonapi/types/anchor_comments.rb +23 -0
  27. data/lib/anchor/inference/jsonapi/types/base.rb +9 -0
  28. data/lib/anchor/inference/jsonapi/types/empty.rb +9 -0
  29. data/lib/anchor/inference/jsonapi/types/overridden.rb +14 -0
  30. data/lib/anchor/inference/jsonapi/types/readable.rb +22 -0
  31. data/lib/anchor/inference/jsonapi/types/relationships_wrapper.rb +29 -0
  32. data/lib/anchor/inference/jsonapi/types.rb +8 -0
  33. data/lib/anchor/json_schema/resource.rb +8 -8
  34. data/lib/anchor/json_schema/schema_generator.rb +1 -3
  35. data/lib/anchor/json_schema/serializer.rb +1 -0
  36. data/lib/anchor/schema.rb +1 -1
  37. data/lib/anchor/schema_generator.rb +1 -2
  38. data/lib/anchor/type_script/file_structure.rb +4 -3
  39. data/lib/anchor/type_script/multifile_schema_generator.rb +1 -4
  40. data/lib/anchor/type_script/resource.rb +17 -15
  41. data/lib/anchor/type_script/schema_generator.rb +1 -3
  42. data/lib/anchor/type_script/serializer.rb +1 -0
  43. data/lib/anchor/types/inference/active_record.rb +11 -35
  44. data/lib/anchor/types/inference/rbs.rb +35 -23
  45. data/lib/anchor/types.rb +123 -11
  46. data/lib/anchor/version.rb +1 -1
  47. data/lib/jsonapi-resources-anchor.rb +35 -2
  48. metadata +32 -4
  49. data/lib/anchor/resource.rb +0 -213
  50. data/lib/anchor/types/inference/jsonapi.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a315147926ca737e06a0b594d33a8a5d435333abfb529c2ae8fade5342a641e
4
- data.tar.gz: 13fd9b832e61b49bec167c58b45700b5b527fee09d4eb555f4dc4a4680395078
3
+ metadata.gz: 4c509ff3d599c7750df7ed4be56c5ec1cc6b72d89e4e9c6b4c1575b342b14a4a
4
+ data.tar.gz: 3def582ca445df2805878e8b9ac378119f82fac569c4ef39835c5e279093918a
5
5
  SHA512:
6
- metadata.gz: 48ad87a68af94a2774d734ac8b591e1e77f25f558706d0b9de4d95d81f136b7edbd5a161c62732d5c5f5e35e94bbf1352dfffc54ab44b2b3b4c7b6dbaf98e4cd
7
- data.tar.gz: 4d0ed1301e0a22b61c867603b8484d7bcf7a925365caa7c90032981f19dab78e2487e3f239bfdd86bf71c75c9bc7cf9888bc62c8c42f27d6237053c64772af65
6
+ metadata.gz: 20f619334d556814e52f4e69352c8b0b2007810792e86324b0555ef8215ea0fe75f4cf3eb34a20103a9a5e02926f1433938ef1b6590226d946299867057b3ef5
7
+ data.tar.gz: f9f5f9ec6ab1c10d7974961669c2bb31199263fa4fb50f3f16b2d90c485ded025c3cd47604494636309af8c52d4e69bce6d0efb74d5ef4cfbdee45fe7eadac70
@@ -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("sig")
29
33
  end
30
34
  end
31
35
  end
@@ -0,0 +1,12 @@
1
+ module Anchor::Inference::ActiveRecord::Infer
2
+ class Base
3
+ include Anchor::Typeable
4
+
5
+ def initialize(klass)
6
+ @klass = klass
7
+ end
8
+
9
+ def self.infer(...) = new(...).infer
10
+ def infer = raise NotImplementedError
11
+ end
12
+ 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,8 @@
1
+ module Anchor
2
+ module Inference
3
+ module ActiveRecord
4
+ module Infer
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Anchor::Inference::ActiveRecord::Types
2
+ class Base
3
+ def initialize(klass)
4
+ @klass = klass
5
+ end
6
+
7
+ def wrap(t) = raise NotImplementedError
8
+ end
9
+ 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,13 @@
1
+ module Anchor::Inference::ActiveRecord::Types
2
+ class Serialized < Base
3
+ def wrap(t) = t.untype(names)
4
+
5
+ private
6
+
7
+ def names
8
+ @klass.attribute_types.filter_map do |name, type|
9
+ name if type.respond_to?(:coder)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ module Anchor
2
+ module Inference
3
+ module ActiveRecord
4
+ module Types
5
+ end
6
+ end
7
+ end
8
+ 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,12 @@
1
+ module Anchor::Inference::JSONAPI::Infer
2
+ class Base
3
+ include Anchor::Typeable
4
+
5
+ def initialize(klass)
6
+ @klass = klass
7
+ end
8
+
9
+ def self.infer(...) = new(...).infer
10
+ def infer = raise NotImplementedError
11
+ end
12
+ 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
@@ -0,0 +1,8 @@
1
+ module Anchor
2
+ module Inference
3
+ module JSONAPI
4
+ module Infer
5
+ end
6
+ end
7
+ end
8
+ end