apiwork 0.0.0.pre → 0.1.2
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/LICENSE.txt +2 -2
- data/README.md +117 -1
- data/Rakefile +5 -3
- data/app/controllers/apiwork/errors_controller.rb +13 -0
- data/app/controllers/apiwork/exports_controller.rb +22 -0
- data/lib/apiwork/abstractable.rb +26 -0
- data/lib/apiwork/adapter/base.rb +369 -0
- data/lib/apiwork/adapter/builder/api/base.rb +66 -0
- data/lib/apiwork/adapter/builder/contract/base.rb +86 -0
- data/lib/apiwork/adapter/capability/api/base.rb +51 -0
- data/lib/apiwork/adapter/capability/api/scope.rb +64 -0
- data/lib/apiwork/adapter/capability/base.rb +291 -0
- data/lib/apiwork/adapter/capability/contract/base.rb +37 -0
- data/lib/apiwork/adapter/capability/contract/scope.rb +110 -0
- data/lib/apiwork/adapter/capability/operation/base.rb +172 -0
- data/lib/apiwork/adapter/capability/operation/metadata_shape.rb +165 -0
- data/lib/apiwork/adapter/capability/result.rb +21 -0
- data/lib/apiwork/adapter/capability/runner.rb +56 -0
- data/lib/apiwork/adapter/capability/transformer/request/base.rb +72 -0
- data/lib/apiwork/adapter/capability/transformer/response/base.rb +45 -0
- data/lib/apiwork/adapter/registry.rb +16 -0
- data/lib/apiwork/adapter/serializer/error/base.rb +72 -0
- data/lib/apiwork/adapter/serializer/error/default/api_builder.rb +32 -0
- data/lib/apiwork/adapter/serializer/error/default.rb +37 -0
- data/lib/apiwork/adapter/serializer/resource/base.rb +84 -0
- data/lib/apiwork/adapter/serializer/resource/default/contract_builder.rb +209 -0
- data/lib/apiwork/adapter/serializer/resource/default.rb +39 -0
- data/lib/apiwork/adapter/standard/capability/filtering/api_builder.rb +75 -0
- data/lib/apiwork/adapter/standard/capability/filtering/constants.rb +37 -0
- data/lib/apiwork/adapter/standard/capability/filtering/contract_builder.rb +193 -0
- data/lib/apiwork/adapter/standard/capability/filtering/operation/filter/builder.rb +47 -0
- data/lib/apiwork/adapter/standard/capability/filtering/operation/filter/operator_builder.rb +36 -0
- data/lib/apiwork/adapter/standard/capability/filtering/operation/filter.rb +462 -0
- data/lib/apiwork/adapter/standard/capability/filtering/operation.rb +22 -0
- data/lib/apiwork/adapter/standard/capability/filtering/request_transformer.rb +47 -0
- data/lib/apiwork/adapter/standard/capability/filtering.rb +18 -0
- data/lib/apiwork/adapter/standard/capability/including/contract_builder.rb +169 -0
- data/lib/apiwork/adapter/standard/capability/including/operation.rb +20 -0
- data/lib/apiwork/adapter/standard/capability/including.rb +16 -0
- data/lib/apiwork/adapter/standard/capability/pagination/api_builder.rb +34 -0
- data/lib/apiwork/adapter/standard/capability/pagination/contract_builder.rb +35 -0
- data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate/cursor.rb +84 -0
- data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate/offset.rb +66 -0
- data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate.rb +24 -0
- data/lib/apiwork/adapter/standard/capability/pagination/operation.rb +24 -0
- data/lib/apiwork/adapter/standard/capability/pagination.rb +21 -0
- data/lib/apiwork/adapter/standard/capability/sorting/api_builder.rb +19 -0
- data/lib/apiwork/adapter/standard/capability/sorting/contract_builder.rb +84 -0
- data/lib/apiwork/adapter/standard/capability/sorting/operation/sort.rb +83 -0
- data/lib/apiwork/adapter/standard/capability/sorting/operation.rb +22 -0
- data/lib/apiwork/adapter/standard/capability/sorting.rb +17 -0
- data/lib/apiwork/adapter/standard/capability/writing/constants.rb +15 -0
- data/lib/apiwork/adapter/standard/capability/writing/contract_builder.rb +253 -0
- data/lib/apiwork/adapter/standard/capability/writing/operation/issue_mapper.rb +210 -0
- data/lib/apiwork/adapter/standard/capability/writing/operation.rb +32 -0
- data/lib/apiwork/adapter/standard/capability/writing/request_transformer.rb +37 -0
- data/lib/apiwork/adapter/standard/capability/writing.rb +17 -0
- data/lib/apiwork/adapter/standard/includes_resolver.rb +106 -0
- data/lib/apiwork/adapter/standard.rb +22 -0
- data/lib/apiwork/adapter/wrapper/base.rb +70 -0
- data/lib/apiwork/adapter/wrapper/collection/base.rb +60 -0
- data/lib/apiwork/adapter/wrapper/collection/default.rb +47 -0
- data/lib/apiwork/adapter/wrapper/error/base.rb +30 -0
- data/lib/apiwork/adapter/wrapper/error/default.rb +34 -0
- data/lib/apiwork/adapter/wrapper/member/base.rb +58 -0
- data/lib/apiwork/adapter/wrapper/member/default.rb +40 -0
- data/lib/apiwork/adapter/wrapper/shape.rb +203 -0
- data/lib/apiwork/adapter.rb +50 -0
- data/lib/apiwork/api/base.rb +802 -0
- data/lib/apiwork/api/element.rb +110 -0
- data/lib/apiwork/api/enum_registry/definition.rb +51 -0
- data/lib/apiwork/api/enum_registry.rb +98 -0
- data/lib/apiwork/api/info/contact.rb +67 -0
- data/lib/apiwork/api/info/license.rb +50 -0
- data/lib/apiwork/api/info/server.rb +50 -0
- data/lib/apiwork/api/info.rb +221 -0
- data/lib/apiwork/api/object.rb +235 -0
- data/lib/apiwork/api/registry.rb +33 -0
- data/lib/apiwork/api/representation_registry.rb +76 -0
- data/lib/apiwork/api/resource/action.rb +41 -0
- data/lib/apiwork/api/resource.rb +648 -0
- data/lib/apiwork/api/router.rb +104 -0
- data/lib/apiwork/api/type_registry/definition.rb +117 -0
- data/lib/apiwork/api/type_registry.rb +99 -0
- data/lib/apiwork/api/union.rb +49 -0
- data/lib/apiwork/api.rb +85 -0
- data/lib/apiwork/configurable.rb +71 -0
- data/lib/apiwork/configuration/option.rb +125 -0
- data/lib/apiwork/configuration/validatable.rb +25 -0
- data/lib/apiwork/configuration.rb +95 -0
- data/lib/apiwork/configuration_error.rb +6 -0
- data/lib/apiwork/constraint_error.rb +20 -0
- data/lib/apiwork/contract/action/request.rb +79 -0
- data/lib/apiwork/contract/action/response.rb +87 -0
- data/lib/apiwork/contract/action.rb +258 -0
- data/lib/apiwork/contract/base.rb +714 -0
- data/lib/apiwork/contract/element.rb +130 -0
- data/lib/apiwork/contract/object/coercer.rb +194 -0
- data/lib/apiwork/contract/object/deserializer.rb +101 -0
- data/lib/apiwork/contract/object/transformer.rb +95 -0
- data/lib/apiwork/contract/object/validator/result.rb +27 -0
- data/lib/apiwork/contract/object/validator.rb +734 -0
- data/lib/apiwork/contract/object.rb +566 -0
- data/lib/apiwork/contract/request_parser/result.rb +25 -0
- data/lib/apiwork/contract/request_parser.rb +72 -0
- data/lib/apiwork/contract/response_parser/result.rb +25 -0
- data/lib/apiwork/contract/response_parser.rb +35 -0
- data/lib/apiwork/contract/union.rb +56 -0
- data/lib/apiwork/contract_error.rb +9 -0
- data/lib/apiwork/controller.rb +300 -0
- data/lib/apiwork/domain_error.rb +13 -0
- data/lib/apiwork/element.rb +386 -0
- data/lib/apiwork/engine.rb +20 -0
- data/lib/apiwork/error.rb +6 -0
- data/lib/apiwork/error_code/definition.rb +63 -0
- data/lib/apiwork/error_code/registry.rb +18 -0
- data/lib/apiwork/error_code.rb +132 -0
- data/lib/apiwork/export/base.rb +291 -0
- data/lib/apiwork/export/open_api.rb +600 -0
- data/lib/apiwork/export/pipeline/writer.rb +66 -0
- data/lib/apiwork/export/pipeline.rb +84 -0
- data/lib/apiwork/export/registry.rb +16 -0
- data/lib/apiwork/export/surface_resolver.rb +189 -0
- data/lib/apiwork/export/type_analysis.rb +170 -0
- data/lib/apiwork/export/type_script.rb +23 -0
- data/lib/apiwork/export/type_script_mapper.rb +349 -0
- data/lib/apiwork/export/zod.rb +39 -0
- data/lib/apiwork/export/zod_mapper.rb +421 -0
- data/lib/apiwork/export.rb +80 -0
- data/lib/apiwork/http_error.rb +16 -0
- data/lib/apiwork/introspection/action/request.rb +66 -0
- data/lib/apiwork/introspection/action/response.rb +57 -0
- data/lib/apiwork/introspection/action.rb +124 -0
- data/lib/apiwork/introspection/api/info/contact.rb +59 -0
- data/lib/apiwork/introspection/api/info/license.rb +49 -0
- data/lib/apiwork/introspection/api/info/server.rb +50 -0
- data/lib/apiwork/introspection/api/info.rb +107 -0
- data/lib/apiwork/introspection/api/resource.rb +83 -0
- data/lib/apiwork/introspection/api.rb +92 -0
- data/lib/apiwork/introspection/contract.rb +63 -0
- data/lib/apiwork/introspection/dump/action.rb +101 -0
- data/lib/apiwork/introspection/dump/api.rb +119 -0
- data/lib/apiwork/introspection/dump/contract.rb +129 -0
- data/lib/apiwork/introspection/dump/param.rb +486 -0
- data/lib/apiwork/introspection/dump/resource.rb +112 -0
- data/lib/apiwork/introspection/dump/type.rb +339 -0
- data/lib/apiwork/introspection/dump.rb +17 -0
- data/lib/apiwork/introspection/enum.rb +63 -0
- data/lib/apiwork/introspection/error_code.rb +44 -0
- data/lib/apiwork/introspection/param/array.rb +88 -0
- data/lib/apiwork/introspection/param/base.rb +285 -0
- data/lib/apiwork/introspection/param/binary.rb +73 -0
- data/lib/apiwork/introspection/param/boolean.rb +73 -0
- data/lib/apiwork/introspection/param/date.rb +73 -0
- data/lib/apiwork/introspection/param/date_time.rb +73 -0
- data/lib/apiwork/introspection/param/decimal.rb +121 -0
- data/lib/apiwork/introspection/param/integer.rb +131 -0
- data/lib/apiwork/introspection/param/literal.rb +45 -0
- data/lib/apiwork/introspection/param/number.rb +121 -0
- data/lib/apiwork/introspection/param/object.rb +59 -0
- data/lib/apiwork/introspection/param/reference.rb +45 -0
- data/lib/apiwork/introspection/param/string.rb +122 -0
- data/lib/apiwork/introspection/param/time.rb +73 -0
- data/lib/apiwork/introspection/param/union.rb +57 -0
- data/lib/apiwork/introspection/param/unknown.rb +26 -0
- data/lib/apiwork/introspection/param/uuid.rb +73 -0
- data/lib/apiwork/introspection/param.rb +31 -0
- data/lib/apiwork/introspection/type.rb +129 -0
- data/lib/apiwork/introspection.rb +28 -0
- data/lib/apiwork/issue.rb +80 -0
- data/lib/apiwork/json_pointer.rb +21 -0
- data/lib/apiwork/object.rb +1618 -0
- data/lib/apiwork/reference_generator.rb +638 -0
- data/lib/apiwork/registry.rb +56 -0
- data/lib/apiwork/representation/association.rb +391 -0
- data/lib/apiwork/representation/attribute.rb +335 -0
- data/lib/apiwork/representation/base.rb +819 -0
- data/lib/apiwork/representation/deserializer.rb +95 -0
- data/lib/apiwork/representation/element.rb +128 -0
- data/lib/apiwork/representation/inheritance.rb +78 -0
- data/lib/apiwork/representation/model_detector.rb +75 -0
- data/lib/apiwork/representation/root_key.rb +35 -0
- data/lib/apiwork/representation/serializer.rb +127 -0
- data/lib/apiwork/request.rb +79 -0
- data/lib/apiwork/response.rb +56 -0
- data/lib/apiwork/union.rb +102 -0
- data/lib/apiwork/version.rb +2 -2
- data/lib/apiwork.rb +61 -3
- data/lib/generators/apiwork/api_generator.rb +38 -0
- data/lib/generators/apiwork/contract_generator.rb +25 -0
- data/lib/generators/apiwork/install_generator.rb +27 -0
- data/lib/generators/apiwork/representation_generator.rb +25 -0
- data/lib/generators/apiwork/templates/api/api.rb.tt +4 -0
- data/lib/generators/apiwork/templates/contract/contract.rb.tt +6 -0
- data/lib/generators/apiwork/templates/install/application_contract.rb.tt +5 -0
- data/lib/generators/apiwork/templates/install/application_representation.rb.tt +5 -0
- data/lib/generators/apiwork/templates/representation/representation.rb.tt +6 -0
- data/lib/tasks/apiwork.rake +102 -0
- metadata +319 -19
- data/.rubocop.yml +0 -8
- data/sig/apiwork.rbs +0 -4
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Representation
|
|
5
|
+
# @api public
|
|
6
|
+
# Base class for representations.
|
|
7
|
+
#
|
|
8
|
+
# Defines how an ActiveRecord model is represented in the API. Drives contracts and runtime behavior.
|
|
9
|
+
# Sensible defaults are auto-detected from database columns but can be overridden. Supports STI and
|
|
10
|
+
# polymorphic associations.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic representation
|
|
13
|
+
# class InvoiceRepresentation < Apiwork::Representation::Base
|
|
14
|
+
# attribute :id
|
|
15
|
+
# attribute :title
|
|
16
|
+
# attribute :status, filterable: true, sortable: true
|
|
17
|
+
#
|
|
18
|
+
# belongs_to :customer
|
|
19
|
+
# has_many :items
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example Contract
|
|
23
|
+
# class InvoiceContract < Apiwork::Contract::Base
|
|
24
|
+
# representation InvoiceRepresentation
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @!scope class
|
|
28
|
+
# @!method abstract!
|
|
29
|
+
# @api public
|
|
30
|
+
# Marks this representation as abstract.
|
|
31
|
+
#
|
|
32
|
+
# Abstract representations don't require a model and serve as base classes for other representations.
|
|
33
|
+
# Use this when creating application-wide base representations. Subclasses automatically become non-abstract.
|
|
34
|
+
# @return [void]
|
|
35
|
+
# @example Application base representation
|
|
36
|
+
# class ApplicationRepresentation < Apiwork::Representation::Base
|
|
37
|
+
# abstract!
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# @!method abstract?
|
|
41
|
+
# @api public
|
|
42
|
+
# Whether this representation is abstract.
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
class Base
|
|
45
|
+
include Abstractable
|
|
46
|
+
|
|
47
|
+
# @!method self.attributes
|
|
48
|
+
# @api public
|
|
49
|
+
# The attributes for this representation.
|
|
50
|
+
#
|
|
51
|
+
# @return [Hash{Symbol => Attribute}]
|
|
52
|
+
class_attribute :attributes, default: {}, instance_accessor: false
|
|
53
|
+
|
|
54
|
+
# @!method self.associations
|
|
55
|
+
# @api public
|
|
56
|
+
# The associations for this representation.
|
|
57
|
+
#
|
|
58
|
+
# @return [Hash{Symbol => Association}]
|
|
59
|
+
class_attribute :associations, default: {}, instance_accessor: false
|
|
60
|
+
|
|
61
|
+
# @!method self.inheritance
|
|
62
|
+
# @api public
|
|
63
|
+
# The inheritance configuration for this representation.
|
|
64
|
+
#
|
|
65
|
+
# Auto-configured when the model uses STI and representation classes mirror the model hierarchy.
|
|
66
|
+
# Subclasses share the parent's inheritance configuration.
|
|
67
|
+
#
|
|
68
|
+
# @return [Representation::Inheritance, nil]
|
|
69
|
+
class_attribute :inheritance, default: nil, instance_accessor: false
|
|
70
|
+
|
|
71
|
+
class_attribute :_adapter_config, default: {}, instance_accessor: false
|
|
72
|
+
|
|
73
|
+
# @!attribute [r] context
|
|
74
|
+
# @api public
|
|
75
|
+
# The serialization context.
|
|
76
|
+
#
|
|
77
|
+
# Passed from controller or directly to {.serialize}. Use for data that isn't on the record, like
|
|
78
|
+
# current user or permissions.
|
|
79
|
+
#
|
|
80
|
+
# @return [Hash]
|
|
81
|
+
#
|
|
82
|
+
# @example Override in controller
|
|
83
|
+
# def context
|
|
84
|
+
# { current_user: current_user }
|
|
85
|
+
# end
|
|
86
|
+
#
|
|
87
|
+
# @example Access in custom attribute
|
|
88
|
+
# attribute :editable, type: :boolean
|
|
89
|
+
#
|
|
90
|
+
# def editable
|
|
91
|
+
# context[:current_user]&.admin?
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# @!attribute [r] record
|
|
95
|
+
# @api public
|
|
96
|
+
# The record for this representation.
|
|
97
|
+
#
|
|
98
|
+
# Available in custom attributes and associations.
|
|
99
|
+
#
|
|
100
|
+
# @return [ActiveRecord::Base]
|
|
101
|
+
#
|
|
102
|
+
# @example Custom attribute
|
|
103
|
+
# attribute :full_name, type: :string
|
|
104
|
+
#
|
|
105
|
+
# def full_name
|
|
106
|
+
# "#{record.first_name} #{record.last_name}"
|
|
107
|
+
# end
|
|
108
|
+
attr_reader :context,
|
|
109
|
+
:record
|
|
110
|
+
|
|
111
|
+
class << self
|
|
112
|
+
# @api public
|
|
113
|
+
# Configures the model class for this representation.
|
|
114
|
+
#
|
|
115
|
+
# Auto-detected from representation name when not set. Use {.model_class} to retrieve.
|
|
116
|
+
#
|
|
117
|
+
# @param value [Class<ActiveRecord::Base>]
|
|
118
|
+
# The model class.
|
|
119
|
+
# @return [void]
|
|
120
|
+
# @raise [ArgumentError] if value is not an ActiveRecord model class
|
|
121
|
+
#
|
|
122
|
+
# @example
|
|
123
|
+
# model Invoice
|
|
124
|
+
def model(value)
|
|
125
|
+
unless value.is_a?(Class)
|
|
126
|
+
raise ConfigurationError,
|
|
127
|
+
"model must be an ActiveRecord model class, got #{value.class}. " \
|
|
128
|
+
"Use: model Post (not 'Post' or :post)"
|
|
129
|
+
end
|
|
130
|
+
unless value < ActiveRecord::Base
|
|
131
|
+
raise ConfigurationError,
|
|
132
|
+
"model must be an ActiveRecord model class, got #{value}"
|
|
133
|
+
end
|
|
134
|
+
@model_class = value
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @api public
|
|
138
|
+
# Configures the JSON root key for this representation.
|
|
139
|
+
#
|
|
140
|
+
# Auto-detected from model name when not set. Use {.root_key} to retrieve.
|
|
141
|
+
#
|
|
142
|
+
# @param singular [String, Symbol]
|
|
143
|
+
# The singular root key.
|
|
144
|
+
# @param plural [String, Symbol] (singular.pluralize)
|
|
145
|
+
# The plural root key.
|
|
146
|
+
# @return [void]
|
|
147
|
+
#
|
|
148
|
+
# @example
|
|
149
|
+
# root :bill, :bills
|
|
150
|
+
def root(singular, plural = singular.to_s.pluralize)
|
|
151
|
+
@root = { plural: plural.to_s, singular: singular.to_s }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @api public
|
|
155
|
+
# Configures adapter options for this representation.
|
|
156
|
+
#
|
|
157
|
+
# Overrides API-level options. Subclasses inherit parent adapter options.
|
|
158
|
+
#
|
|
159
|
+
# @yieldparam adapter [Configuration]
|
|
160
|
+
# @return [void]
|
|
161
|
+
#
|
|
162
|
+
# @example
|
|
163
|
+
# adapter do
|
|
164
|
+
# pagination do
|
|
165
|
+
# strategy :cursor
|
|
166
|
+
# default_size 50
|
|
167
|
+
# end
|
|
168
|
+
# end
|
|
169
|
+
def adapter(&block)
|
|
170
|
+
return unless block
|
|
171
|
+
|
|
172
|
+
self._adapter_config = _adapter_config.dup
|
|
173
|
+
config = Configuration.new(api_class.adapter_class, _adapter_config)
|
|
174
|
+
block.arity.positive? ? yield(config) : config.instance_eval(&block)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @api public
|
|
178
|
+
# Defines an attribute for this representation.
|
|
179
|
+
#
|
|
180
|
+
# Subclasses inherit parent attributes.
|
|
181
|
+
#
|
|
182
|
+
# @param name [Symbol]
|
|
183
|
+
# The attribute name.
|
|
184
|
+
# @param decode [Proc, nil] (nil)
|
|
185
|
+
# Transform for request input (API to database). Must preserve the attribute type.
|
|
186
|
+
# @param deprecated [Boolean] (false)
|
|
187
|
+
# Whether deprecated. Metadata included in exports.
|
|
188
|
+
# @param description [String, nil] (nil)
|
|
189
|
+
# The description. Metadata included in exports.
|
|
190
|
+
# @param empty [Boolean, nil] (nil)
|
|
191
|
+
# Whether to use empty string instead of `null`. Serializes `nil` as `""` and deserializes `""` as `nil`. Only valid for `:string` type.
|
|
192
|
+
# @param encode [Proc, nil] (nil)
|
|
193
|
+
# Transform for response output (database to API). Must preserve the attribute type.
|
|
194
|
+
# @param enum [Array, nil] (nil)
|
|
195
|
+
# The allowed values. If `nil`, auto-detected from Rails enum definition.
|
|
196
|
+
# @param example [Object, nil] (nil)
|
|
197
|
+
# The example. Metadata included in exports.
|
|
198
|
+
# @param filterable [Boolean] (false)
|
|
199
|
+
# Whether the attribute is filterable.
|
|
200
|
+
# @param format [Symbol, nil] (nil) [:date, :datetime, :double, :email, :float, :hostname, :int32, :int64, :ipv4, :ipv6, :password, :url, :uuid]
|
|
201
|
+
# Format hint for exports. Does not change the type, but exports may add validation or
|
|
202
|
+
# documentation based on it. Valid formats by type: `:decimal`/`:number` (`:double`, `:float`),
|
|
203
|
+
# `:integer` (`:int32`, `:int64`), `:string` (`:date`, `:datetime`, `:email`, `:hostname`,
|
|
204
|
+
# `:ipv4`, `:ipv6`, `:password`, `:url`, `:uuid`).
|
|
205
|
+
# @param max [Integer, nil] (nil)
|
|
206
|
+
# The maximum. For `:array`: size. For `:decimal`, `:integer`, `:number`: value. For `:string`: length.
|
|
207
|
+
# @param min [Integer, nil] (nil)
|
|
208
|
+
# The minimum. For `:array`: size. For `:decimal`, `:integer`, `:number`: value. For `:string`: length.
|
|
209
|
+
# @param nullable [Boolean, nil] (nil)
|
|
210
|
+
# Whether the value can be `null`. If `nil` and name maps to a database column, auto-detected from column NULL constraint.
|
|
211
|
+
# @param optional [Boolean, nil] (nil)
|
|
212
|
+
# Whether the attribute is optional for writes. If `nil` and name maps to a database column,
|
|
213
|
+
# auto-detected from column default or NULL constraint.
|
|
214
|
+
# @param preload [Symbol, Array, Hash, nil] (nil)
|
|
215
|
+
# Associations to preload for this attribute. Use when custom attributes depend on associations.
|
|
216
|
+
# @param sortable [Boolean] (false)
|
|
217
|
+
# Whether the attribute is sortable.
|
|
218
|
+
# @param type [Symbol, nil] (nil) [:array, :binary, :boolean, :date, :datetime, :decimal, :integer, :number, :object, :string, :time, :unknown, :uuid]
|
|
219
|
+
# The type. If `nil` and name maps to a database column, auto-detected from column type.
|
|
220
|
+
# Defaults to `:unknown` for json/jsonb columns and when no column exists (custom attributes).
|
|
221
|
+
# Use an explicit type or block in those cases.
|
|
222
|
+
# @param writable [Boolean, Symbol] (false) [:create, :update]
|
|
223
|
+
# The write access. `true` for both create and update, `:create` for create only, `:update` for update only.
|
|
224
|
+
# @yieldparam element [Representation::Element]
|
|
225
|
+
# @return [void]
|
|
226
|
+
#
|
|
227
|
+
# @example Basic
|
|
228
|
+
# attribute :title
|
|
229
|
+
# attribute :price, type: :decimal, min: 0
|
|
230
|
+
# attribute :status, filterable: true, sortable: true
|
|
231
|
+
#
|
|
232
|
+
# @example Custom attribute with preload
|
|
233
|
+
# attribute :total, type: :decimal, preload: :items
|
|
234
|
+
#
|
|
235
|
+
# def total
|
|
236
|
+
# record.items.sum(:amount)
|
|
237
|
+
# end
|
|
238
|
+
#
|
|
239
|
+
# @example Nested preload
|
|
240
|
+
# attribute :total_with_tax, type: :decimal, preload: { items: :tax_rate }
|
|
241
|
+
#
|
|
242
|
+
# def total_with_tax
|
|
243
|
+
# record.items.sum { |item| item.amount * (1 + item.tax_rate.rate) }
|
|
244
|
+
# end
|
|
245
|
+
#
|
|
246
|
+
# @example Inline type for JSON column
|
|
247
|
+
# attribute :settings do
|
|
248
|
+
# object do
|
|
249
|
+
# string :theme
|
|
250
|
+
# boolean :notifications
|
|
251
|
+
# end
|
|
252
|
+
# end
|
|
253
|
+
#
|
|
254
|
+
# @example Encode/decode transforms
|
|
255
|
+
# attribute :status, encode: ->(value) { value.upcase }, decode: ->(value) { value.downcase }
|
|
256
|
+
#
|
|
257
|
+
# @example Writable only on create
|
|
258
|
+
# attribute :slug, writable: :create
|
|
259
|
+
#
|
|
260
|
+
# @example Explicit enum values
|
|
261
|
+
# attribute :priority, enum: [:low, :medium, :high]
|
|
262
|
+
#
|
|
263
|
+
# @example Multiple preloads
|
|
264
|
+
# attribute :summary, type: :string, preload: [:items, :customer]
|
|
265
|
+
#
|
|
266
|
+
# def summary
|
|
267
|
+
# "#{record.customer.name}: #{record.items.count} items"
|
|
268
|
+
# end
|
|
269
|
+
def attribute(
|
|
270
|
+
name,
|
|
271
|
+
decode: nil,
|
|
272
|
+
deprecated: false,
|
|
273
|
+
description: nil,
|
|
274
|
+
empty: nil,
|
|
275
|
+
encode: nil,
|
|
276
|
+
enum: nil,
|
|
277
|
+
example: nil,
|
|
278
|
+
filterable: false,
|
|
279
|
+
format: nil,
|
|
280
|
+
max: nil,
|
|
281
|
+
min: nil,
|
|
282
|
+
nullable: nil,
|
|
283
|
+
optional: nil,
|
|
284
|
+
preload: nil,
|
|
285
|
+
sortable: false,
|
|
286
|
+
type: nil,
|
|
287
|
+
writable: false,
|
|
288
|
+
&block
|
|
289
|
+
)
|
|
290
|
+
self.attributes = attributes.merge(
|
|
291
|
+
name => Attribute.new(
|
|
292
|
+
name,
|
|
293
|
+
self,
|
|
294
|
+
decode:,
|
|
295
|
+
deprecated:,
|
|
296
|
+
description:,
|
|
297
|
+
empty:,
|
|
298
|
+
encode:,
|
|
299
|
+
enum:,
|
|
300
|
+
example:,
|
|
301
|
+
filterable:,
|
|
302
|
+
format:,
|
|
303
|
+
max:,
|
|
304
|
+
min:,
|
|
305
|
+
nullable:,
|
|
306
|
+
optional:,
|
|
307
|
+
preload:,
|
|
308
|
+
sortable:,
|
|
309
|
+
type:,
|
|
310
|
+
writable:,
|
|
311
|
+
&block
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# @api public
|
|
317
|
+
# Defines a has_one association for this representation.
|
|
318
|
+
#
|
|
319
|
+
# Subclasses inherit parent associations.
|
|
320
|
+
#
|
|
321
|
+
# @param name [Symbol]
|
|
322
|
+
# The association name.
|
|
323
|
+
# @param deprecated [Boolean] (false)
|
|
324
|
+
# Whether deprecated. Metadata included in exports.
|
|
325
|
+
# @param description [String, nil] (nil)
|
|
326
|
+
# The description. Metadata included in exports.
|
|
327
|
+
# @param example [Object, nil] (nil)
|
|
328
|
+
# The example. Metadata included in exports.
|
|
329
|
+
# @param filterable [Boolean] (false)
|
|
330
|
+
# Whether the association is filterable.
|
|
331
|
+
# @param include [Symbol] (:optional) [:always, :optional]
|
|
332
|
+
# The inclusion mode.
|
|
333
|
+
# @param nullable [Boolean, nil] (nil)
|
|
334
|
+
# Whether the association can be `null`.
|
|
335
|
+
# @param representation [Class<Representation::Base>, nil] (nil)
|
|
336
|
+
# The representation class. If `nil`, inferred from the associated model in the same
|
|
337
|
+
# namespace (e.g., `CustomerRepresentation` for `Customer`).
|
|
338
|
+
# @param sortable [Boolean] (false)
|
|
339
|
+
# Whether the association is sortable.
|
|
340
|
+
# @param writable [Boolean, Symbol] (false) [:create, :update]
|
|
341
|
+
# The write access. `true` for both create and update, `:create` for create only, `:update` for update only.
|
|
342
|
+
# Requires `accepts_nested_attributes_for` on the model, where `allow_destroy: true` also enables deletion.
|
|
343
|
+
# @return [void]
|
|
344
|
+
#
|
|
345
|
+
# @example Basic
|
|
346
|
+
# has_one :profile
|
|
347
|
+
#
|
|
348
|
+
# @example Explicit representation
|
|
349
|
+
# has_one :author, representation: AuthorRepresentation
|
|
350
|
+
#
|
|
351
|
+
# @example Always included
|
|
352
|
+
# has_one :customer, include: :always
|
|
353
|
+
#
|
|
354
|
+
# @example Custom association
|
|
355
|
+
# has_one :profile
|
|
356
|
+
#
|
|
357
|
+
# def profile
|
|
358
|
+
# record.profile || record.build_profile
|
|
359
|
+
# end
|
|
360
|
+
def has_one(
|
|
361
|
+
name,
|
|
362
|
+
deprecated: false,
|
|
363
|
+
description: nil,
|
|
364
|
+
example: nil,
|
|
365
|
+
filterable: false,
|
|
366
|
+
include: :optional,
|
|
367
|
+
nullable: nil,
|
|
368
|
+
representation: nil,
|
|
369
|
+
sortable: false,
|
|
370
|
+
writable: false
|
|
371
|
+
)
|
|
372
|
+
self.associations = associations.merge(
|
|
373
|
+
name => Association.new(
|
|
374
|
+
name,
|
|
375
|
+
:has_one,
|
|
376
|
+
self,
|
|
377
|
+
deprecated:,
|
|
378
|
+
description:,
|
|
379
|
+
example:,
|
|
380
|
+
filterable:,
|
|
381
|
+
include:,
|
|
382
|
+
nullable:,
|
|
383
|
+
representation:,
|
|
384
|
+
sortable:,
|
|
385
|
+
writable:,
|
|
386
|
+
),
|
|
387
|
+
)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# @api public
|
|
391
|
+
# Defines a has_many association for this representation.
|
|
392
|
+
#
|
|
393
|
+
# Subclasses inherit parent associations.
|
|
394
|
+
#
|
|
395
|
+
# @param name [Symbol]
|
|
396
|
+
# The association name.
|
|
397
|
+
# @param deprecated [Boolean] (false)
|
|
398
|
+
# Whether deprecated. Metadata included in exports.
|
|
399
|
+
# @param description [String, nil] (nil)
|
|
400
|
+
# The description. Metadata included in exports.
|
|
401
|
+
# @param example [Object, nil] (nil)
|
|
402
|
+
# The example. Metadata included in exports.
|
|
403
|
+
# @param filterable [Boolean] (false)
|
|
404
|
+
# Whether the association is filterable.
|
|
405
|
+
# @param include [Symbol] (:optional) [:always, :optional]
|
|
406
|
+
# The inclusion mode.
|
|
407
|
+
# @param representation [Class<Representation::Base>, nil] (nil)
|
|
408
|
+
# The representation class. If `nil`, inferred from the associated model in the same
|
|
409
|
+
# namespace (e.g., `CustomerRepresentation` for `Customer`).
|
|
410
|
+
# @param sortable [Boolean] (false)
|
|
411
|
+
# Whether the association is sortable.
|
|
412
|
+
# @param writable [Boolean, Symbol] (false) [:create, :update]
|
|
413
|
+
# The write access. `true` for both create and update, `:create` for create only, `:update` for update only.
|
|
414
|
+
# Requires `accepts_nested_attributes_for` on the model, where `allow_destroy: true` also enables deletion.
|
|
415
|
+
# @return [void]
|
|
416
|
+
# @see #has_one
|
|
417
|
+
#
|
|
418
|
+
# @example Basic
|
|
419
|
+
# has_many :items
|
|
420
|
+
#
|
|
421
|
+
# @example Explicit representation
|
|
422
|
+
# has_many :comments, representation: CommentRepresentation
|
|
423
|
+
#
|
|
424
|
+
# @example Always included
|
|
425
|
+
# has_many :items, include: :always
|
|
426
|
+
#
|
|
427
|
+
# @example Custom association
|
|
428
|
+
# has_many :items
|
|
429
|
+
#
|
|
430
|
+
# def items
|
|
431
|
+
# record.items.limit(5)
|
|
432
|
+
# end
|
|
433
|
+
def has_many(
|
|
434
|
+
name,
|
|
435
|
+
deprecated: false,
|
|
436
|
+
description: nil,
|
|
437
|
+
example: nil,
|
|
438
|
+
filterable: false,
|
|
439
|
+
include: :optional,
|
|
440
|
+
representation: nil,
|
|
441
|
+
sortable: false,
|
|
442
|
+
writable: false
|
|
443
|
+
)
|
|
444
|
+
self.associations = associations.merge(
|
|
445
|
+
name => Association.new(
|
|
446
|
+
name,
|
|
447
|
+
:has_many,
|
|
448
|
+
self,
|
|
449
|
+
deprecated:,
|
|
450
|
+
description:,
|
|
451
|
+
example:,
|
|
452
|
+
filterable:,
|
|
453
|
+
include:,
|
|
454
|
+
representation:,
|
|
455
|
+
sortable:,
|
|
456
|
+
writable:,
|
|
457
|
+
),
|
|
458
|
+
)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# @api public
|
|
462
|
+
# Defines a belongs_to association for this representation.
|
|
463
|
+
#
|
|
464
|
+
# Subclasses inherit parent associations.
|
|
465
|
+
#
|
|
466
|
+
# @param name [Symbol]
|
|
467
|
+
# The association name.
|
|
468
|
+
# @param deprecated [Boolean] (false)
|
|
469
|
+
# Whether deprecated. Metadata included in exports.
|
|
470
|
+
# @param description [String, nil] (nil)
|
|
471
|
+
# The description. Metadata included in exports.
|
|
472
|
+
# @param example [Object, nil] (nil)
|
|
473
|
+
# The example. Metadata included in exports.
|
|
474
|
+
# @param filterable [Boolean] (false)
|
|
475
|
+
# Whether the association is filterable.
|
|
476
|
+
# @param include [Symbol] (:optional) [:always, :optional]
|
|
477
|
+
# The inclusion mode.
|
|
478
|
+
# @param nullable [Boolean, nil] (nil)
|
|
479
|
+
# Whether the association can be `null`. If `nil`, auto-detected from foreign key column NULL constraint.
|
|
480
|
+
# @param polymorphic [Array<Class<Representation::Base>>, nil] (nil)
|
|
481
|
+
# The allowed representation classes for polymorphic associations.
|
|
482
|
+
# @param representation [Class<Representation::Base>, nil] (nil)
|
|
483
|
+
# The representation class. If `nil`, inferred from the associated model in the same
|
|
484
|
+
# namespace (e.g., `CustomerRepresentation` for `Customer`).
|
|
485
|
+
# @param sortable [Boolean] (false)
|
|
486
|
+
# Whether the association is sortable.
|
|
487
|
+
# @param writable [Boolean, Symbol] (false) [:create, :update]
|
|
488
|
+
# The write access. `true` for both create and update, `:create` for create only, `:update` for update only.
|
|
489
|
+
# Requires `accepts_nested_attributes_for` on the model, where `allow_destroy: true` also enables deletion.
|
|
490
|
+
# @return [void]
|
|
491
|
+
# @see #has_one
|
|
492
|
+
#
|
|
493
|
+
# @example Basic
|
|
494
|
+
# belongs_to :customer
|
|
495
|
+
#
|
|
496
|
+
# @example Explicit representation
|
|
497
|
+
# belongs_to :author, representation: AuthorRepresentation
|
|
498
|
+
#
|
|
499
|
+
# @example Always included
|
|
500
|
+
# belongs_to :customer, include: :always
|
|
501
|
+
#
|
|
502
|
+
# @example Polymorphic
|
|
503
|
+
# belongs_to :commentable, polymorphic: [PostRepresentation, CustomerRepresentation]
|
|
504
|
+
#
|
|
505
|
+
# @example Custom association
|
|
506
|
+
# belongs_to :customer
|
|
507
|
+
#
|
|
508
|
+
# def customer
|
|
509
|
+
# record.customer || Customer.default
|
|
510
|
+
# end
|
|
511
|
+
def belongs_to(
|
|
512
|
+
name,
|
|
513
|
+
deprecated: false,
|
|
514
|
+
description: nil,
|
|
515
|
+
example: nil,
|
|
516
|
+
filterable: false,
|
|
517
|
+
include: :optional,
|
|
518
|
+
nullable: nil,
|
|
519
|
+
polymorphic: nil,
|
|
520
|
+
representation: nil,
|
|
521
|
+
sortable: false,
|
|
522
|
+
writable: false
|
|
523
|
+
)
|
|
524
|
+
self.associations = associations.merge(
|
|
525
|
+
name => Association.new(
|
|
526
|
+
name,
|
|
527
|
+
:belongs_to,
|
|
528
|
+
self,
|
|
529
|
+
deprecated:,
|
|
530
|
+
description:,
|
|
531
|
+
example:,
|
|
532
|
+
filterable:,
|
|
533
|
+
include:,
|
|
534
|
+
nullable:,
|
|
535
|
+
polymorphic:,
|
|
536
|
+
representation:,
|
|
537
|
+
sortable:,
|
|
538
|
+
writable:,
|
|
539
|
+
),
|
|
540
|
+
)
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# @api public
|
|
544
|
+
# The type name for this representation.
|
|
545
|
+
#
|
|
546
|
+
# Overrides the model's default for STI and polymorphic types.
|
|
547
|
+
#
|
|
548
|
+
# @param value [String, Symbol, nil] (nil)
|
|
549
|
+
# The type name.
|
|
550
|
+
# @return [String, nil]
|
|
551
|
+
# @see .sti_name
|
|
552
|
+
# @see .polymorphic_name
|
|
553
|
+
#
|
|
554
|
+
# @example
|
|
555
|
+
# type_name :person
|
|
556
|
+
def type_name(value = nil)
|
|
557
|
+
return @type_name = value.to_s if value
|
|
558
|
+
|
|
559
|
+
@type_name
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# @api public
|
|
563
|
+
# The STI name for this representation.
|
|
564
|
+
#
|
|
565
|
+
# Uses {.type_name} if set, otherwise the model's `sti_name`.
|
|
566
|
+
#
|
|
567
|
+
# @return [String]
|
|
568
|
+
def sti_name
|
|
569
|
+
@type_name || model_class.sti_name
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
# @api public
|
|
573
|
+
# The polymorphic name for this representation.
|
|
574
|
+
#
|
|
575
|
+
# Uses {.type_name} if set, otherwise the model's `polymorphic_name`.
|
|
576
|
+
#
|
|
577
|
+
# @return [String]
|
|
578
|
+
def polymorphic_name
|
|
579
|
+
@type_name || model_class.polymorphic_name
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# @api public
|
|
583
|
+
# Whether this representation is an STI subclass.
|
|
584
|
+
#
|
|
585
|
+
# @return [Boolean]
|
|
586
|
+
def subclass?
|
|
587
|
+
superclass.respond_to?(:inheritance) && superclass.inheritance&.subclass?(self)
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# @api public
|
|
591
|
+
# The description for this representation.
|
|
592
|
+
#
|
|
593
|
+
# Metadata included in exports.
|
|
594
|
+
#
|
|
595
|
+
# @param value [String, nil] (nil)
|
|
596
|
+
# The description.
|
|
597
|
+
# @return [String, nil]
|
|
598
|
+
#
|
|
599
|
+
# @example
|
|
600
|
+
# description 'A customer invoice'
|
|
601
|
+
def description(value = nil)
|
|
602
|
+
return @description if value.nil?
|
|
603
|
+
|
|
604
|
+
@description = value
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# @api public
|
|
608
|
+
# Marks this representation as deprecated.
|
|
609
|
+
#
|
|
610
|
+
# Metadata included in exports.
|
|
611
|
+
#
|
|
612
|
+
# @return [void]
|
|
613
|
+
#
|
|
614
|
+
# @example
|
|
615
|
+
# deprecated!
|
|
616
|
+
def deprecated!
|
|
617
|
+
@deprecated = true
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# @api public
|
|
621
|
+
# The example value for this representation.
|
|
622
|
+
#
|
|
623
|
+
# Metadata included in exports.
|
|
624
|
+
#
|
|
625
|
+
# @param value [Hash, nil] (nil)
|
|
626
|
+
# The example.
|
|
627
|
+
# @return [Hash, nil]
|
|
628
|
+
#
|
|
629
|
+
# @example
|
|
630
|
+
# example id: 1, total: 99.00, status: 'paid'
|
|
631
|
+
def example(value = nil)
|
|
632
|
+
return @example if value.nil?
|
|
633
|
+
|
|
634
|
+
@example = value
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
# @api public
|
|
638
|
+
# Transforms a record or an array of records to hashes.
|
|
639
|
+
#
|
|
640
|
+
# Applies attribute encoders, maps STI and polymorphic type names,
|
|
641
|
+
# and recursively serializes nested associations.
|
|
642
|
+
#
|
|
643
|
+
# @param resource [ActiveRecord::Base, Array<ActiveRecord::Base>]
|
|
644
|
+
# The resource to serialize.
|
|
645
|
+
# @param context [Hash] ({})
|
|
646
|
+
# The serialization context.
|
|
647
|
+
# @param include [Symbol, Array, Hash, nil] (nil)
|
|
648
|
+
# The associations to include.
|
|
649
|
+
# @return [Hash, Array<Hash>]
|
|
650
|
+
#
|
|
651
|
+
# @example Basic
|
|
652
|
+
# InvoiceRepresentation.serialize(invoice)
|
|
653
|
+
# # => { id: 1, total: 99.00, status: 'paid' }
|
|
654
|
+
#
|
|
655
|
+
# @example Collection
|
|
656
|
+
# InvoiceRepresentation.serialize(invoices)
|
|
657
|
+
# # => [{ id: 1, ... }, { id: 2, ... }]
|
|
658
|
+
#
|
|
659
|
+
# @example With associations
|
|
660
|
+
# InvoiceRepresentation.serialize(invoice, include: [:customer, :items])
|
|
661
|
+
# # => { id: 1, ..., customer: { id: 1, name: 'Acme' }, items: [...] }
|
|
662
|
+
#
|
|
663
|
+
# @example Nested associations
|
|
664
|
+
# InvoiceRepresentation.serialize(invoice, include: { customer: [:address] })
|
|
665
|
+
# # => { id: 1, ..., customer: { id: 1, name: 'Acme', address: { ... } } }
|
|
666
|
+
def serialize(resource, context: {}, include: nil)
|
|
667
|
+
if resource.is_a?(Enumerable)
|
|
668
|
+
resource.map { |record| serialize_record(record, context:, include:) }
|
|
669
|
+
else
|
|
670
|
+
serialize_record(resource, context:, include:)
|
|
671
|
+
end
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# @api public
|
|
675
|
+
# Transforms a hash or an array of hashes for records.
|
|
676
|
+
#
|
|
677
|
+
# Applies attribute decoders, maps STI and polymorphic type names,
|
|
678
|
+
# and recursively deserializes nested associations.
|
|
679
|
+
#
|
|
680
|
+
# @param payload [Hash, Array<Hash>]
|
|
681
|
+
# The payload to deserialize.
|
|
682
|
+
# @return [Hash, Array<Hash>]
|
|
683
|
+
#
|
|
684
|
+
# @example
|
|
685
|
+
# InvoiceRepresentation.deserialize(params[:invoice])
|
|
686
|
+
def deserialize(payload)
|
|
687
|
+
Deserializer.deserialize(self, payload)
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
# @api public
|
|
691
|
+
# The root key for this representation.
|
|
692
|
+
#
|
|
693
|
+
# Derived from model name when {.root} is not set.
|
|
694
|
+
#
|
|
695
|
+
# @return [RootKey]
|
|
696
|
+
def root_key
|
|
697
|
+
if @root
|
|
698
|
+
RootKey.new(@root[:singular], @root[:plural])
|
|
699
|
+
else
|
|
700
|
+
RootKey.new(model_class.model_name.element)
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# @api public
|
|
705
|
+
# The model class for this representation.
|
|
706
|
+
#
|
|
707
|
+
# Auto-detected from representation name or set via {.model}.
|
|
708
|
+
#
|
|
709
|
+
# @return [Class<ActiveRecord::Base>]
|
|
710
|
+
def model_class
|
|
711
|
+
ensure_auto_detection_complete
|
|
712
|
+
ensure_sti_auto_configuration_complete
|
|
713
|
+
@model_class
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
# @api public
|
|
717
|
+
# Whether this representation is deprecated.
|
|
718
|
+
#
|
|
719
|
+
# @return [Boolean]
|
|
720
|
+
def deprecated?
|
|
721
|
+
@deprecated == true
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def adapter_config
|
|
725
|
+
@adapter_config ||= api_class.adapter_config.merge(_adapter_config)
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
def api_class
|
|
729
|
+
return nil unless name
|
|
730
|
+
|
|
731
|
+
namespace = name.deconstantize
|
|
732
|
+
return nil if namespace.blank?
|
|
733
|
+
|
|
734
|
+
API.find("/#{namespace.underscore.tr('::', '/')}")
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
def preloads
|
|
738
|
+
attributes.values.filter_map(&:preload)
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
def polymorphic_association_for_type_column(column_name)
|
|
742
|
+
associations.values.find do |association|
|
|
743
|
+
association.polymorphic? && association.discriminator == column_name
|
|
744
|
+
end
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
def inheritance_for_column(column_name)
|
|
748
|
+
target_class = subclass? ? superclass : self
|
|
749
|
+
target_inheritance = target_class.inheritance
|
|
750
|
+
target_model = target_class.model_class
|
|
751
|
+
|
|
752
|
+
return nil unless target_inheritance&.subclasses&.any?
|
|
753
|
+
return nil unless target_model.respond_to?(:inheritance_column)
|
|
754
|
+
|
|
755
|
+
target_inheritance if column_name.to_sym == target_model.inheritance_column.to_sym
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
def serialize_record(record, context: {}, include: nil)
|
|
759
|
+
subclass_representation_class = inheritance.resolve(record) if inheritance&.subclasses&.any?
|
|
760
|
+
representation_class = subclass_representation_class || self
|
|
761
|
+
|
|
762
|
+
representation_class.new(record, context:, include:).as_json
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
private
|
|
766
|
+
|
|
767
|
+
def ensure_auto_detection_complete
|
|
768
|
+
return if @auto_detection_complete
|
|
769
|
+
|
|
770
|
+
@auto_detection_complete = true
|
|
771
|
+
return if @model_class.present?
|
|
772
|
+
|
|
773
|
+
detected = ModelDetector.new(self).detect
|
|
774
|
+
@model_class = detected if detected
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
def ensure_sti_auto_configuration_complete
|
|
778
|
+
return if @sti_auto_configuration_complete
|
|
779
|
+
|
|
780
|
+
@sti_auto_configuration_complete = true
|
|
781
|
+
ensure_auto_detection_complete
|
|
782
|
+
return unless @model_class
|
|
783
|
+
|
|
784
|
+
model_detector = ModelDetector.new(self)
|
|
785
|
+
|
|
786
|
+
auto_configure_inheritance if model_detector.sti_base?(@model_class) && inheritance.nil?
|
|
787
|
+
|
|
788
|
+
return unless model_detector.sti_subclass?(@model_class)
|
|
789
|
+
return unless model_detector.superclass_is_sti_base?(@model_class)
|
|
790
|
+
return if subclass?
|
|
791
|
+
|
|
792
|
+
auto_register_subclass
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
def auto_configure_inheritance
|
|
796
|
+
self.inheritance = Inheritance.new(self)
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
def auto_register_subclass
|
|
800
|
+
superclass.send(:ensure_sti_auto_configuration_complete)
|
|
801
|
+
return unless superclass.inheritance
|
|
802
|
+
|
|
803
|
+
superclass.inheritance.register(self)
|
|
804
|
+
superclass._abstract = true
|
|
805
|
+
end
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
def initialize(record, context: {}, include: nil)
|
|
809
|
+
@record = record
|
|
810
|
+
@context = context
|
|
811
|
+
@include = include
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
def as_json
|
|
815
|
+
Serializer.serialize(self, @include)
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
end
|
|
819
|
+
end
|