active_model_serializers 0.8.3 → 0.10.15
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 +5 -5
- data/CHANGELOG.md +726 -6
- data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
- data/README.md +194 -545
- data/lib/action_controller/serialization.rb +53 -38
- data/lib/active_model/serializable_resource.rb +13 -0
- data/lib/active_model/serializer/adapter/attributes.rb +17 -0
- data/lib/active_model/serializer/adapter/base.rb +20 -0
- data/lib/active_model/serializer/adapter/json.rb +17 -0
- data/lib/active_model/serializer/adapter/json_api.rb +17 -0
- data/lib/active_model/serializer/adapter/null.rb +17 -0
- data/lib/active_model/serializer/adapter.rb +26 -0
- data/lib/active_model/serializer/array_serializer.rb +14 -0
- data/lib/active_model/serializer/association.rb +73 -0
- data/lib/active_model/serializer/attribute.rb +27 -0
- data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
- data/lib/active_model/serializer/collection_serializer.rb +99 -0
- data/lib/active_model/serializer/concerns/caching.rb +305 -0
- data/lib/active_model/serializer/error_serializer.rb +16 -0
- data/lib/active_model/serializer/errors_serializer.rb +34 -0
- data/lib/active_model/serializer/field.rb +92 -0
- data/lib/active_model/serializer/fieldset.rb +33 -0
- data/lib/active_model/serializer/has_many_reflection.rb +12 -0
- data/lib/active_model/serializer/has_one_reflection.rb +9 -0
- data/lib/active_model/serializer/lazy_association.rb +99 -0
- data/lib/active_model/serializer/link.rb +23 -0
- data/lib/active_model/serializer/lint.rb +152 -0
- data/lib/active_model/serializer/null.rb +19 -0
- data/lib/active_model/serializer/reflection.rb +212 -0
- data/lib/active_model/serializer/version.rb +7 -0
- data/lib/active_model/serializer.rb +354 -442
- data/lib/active_model_serializers/adapter/attributes.rb +36 -0
- data/lib/active_model_serializers/adapter/base.rb +85 -0
- data/lib/active_model_serializers/adapter/json.rb +23 -0
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
- data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
- data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
- data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
- data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +94 -0
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
- data/lib/active_model_serializers/adapter/json_api.rb +535 -0
- data/lib/active_model_serializers/adapter/null.rb +11 -0
- data/lib/active_model_serializers/adapter.rb +100 -0
- data/lib/active_model_serializers/callbacks.rb +57 -0
- data/lib/active_model_serializers/deprecate.rb +56 -0
- data/lib/active_model_serializers/deserialization.rb +17 -0
- data/lib/active_model_serializers/json_pointer.rb +16 -0
- data/lib/active_model_serializers/logging.rb +124 -0
- data/lib/active_model_serializers/lookup_chain.rb +82 -0
- data/lib/active_model_serializers/model.rb +132 -0
- data/lib/active_model_serializers/railtie.rb +62 -0
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
- data/lib/active_model_serializers/serializable_resource.rb +84 -0
- data/lib/active_model_serializers/serialization_context.rb +41 -0
- data/lib/active_model_serializers/test/schema.rb +140 -0
- data/lib/active_model_serializers/test/serializer.rb +127 -0
- data/lib/active_model_serializers/test.rb +9 -0
- data/lib/active_model_serializers.rb +49 -81
- data/lib/generators/rails/USAGE +6 -0
- data/lib/generators/rails/resource_override.rb +12 -0
- data/lib/generators/rails/serializer_generator.rb +38 -0
- data/lib/generators/rails/templates/serializer.rb.erb +8 -0
- data/lib/grape/active_model_serializers.rb +18 -0
- data/lib/grape/formatters/active_model_serializers.rb +34 -0
- data/lib/grape/helpers/active_model_serializers.rb +19 -0
- data/lib/tasks/rubocop.rake +60 -0
- metadata +240 -51
- data/.gitignore +0 -18
- data/.travis.yml +0 -28
- data/DESIGN.textile +0 -586
- data/Gemfile +0 -4
- data/Gemfile.edge +0 -9
- data/Rakefile +0 -18
- data/active_model_serializers.gemspec +0 -24
- data/bench/perf.rb +0 -43
- data/cruft.md +0 -19
- data/lib/active_model/array_serializer.rb +0 -104
- data/lib/active_model/serializer/associations.rb +0 -233
- data/lib/active_model/serializers/version.rb +0 -5
- data/lib/active_record/serializer_override.rb +0 -16
- data/lib/generators/resource_override.rb +0 -13
- data/lib/generators/serializer/USAGE +0 -9
- data/lib/generators/serializer/serializer_generator.rb +0 -42
- data/lib/generators/serializer/templates/serializer.rb +0 -19
- data/test/array_serializer_test.rb +0 -75
- data/test/association_test.rb +0 -592
- data/test/caching_test.rb +0 -96
- data/test/generators_test.rb +0 -85
- data/test/no_serialization_scope_test.rb +0 -34
- data/test/serialization_scope_name_test.rb +0 -67
- data/test/serialization_test.rb +0 -392
- data/test/serializer_support_test.rb +0 -51
- data/test/serializer_test.rb +0 -1465
- data/test/test_fakes.rb +0 -217
- data/test/test_helper.rb +0 -32
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModelSerializers
|
4
|
+
module Adapter
|
5
|
+
class Attributes < Base
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
instance_options[:fieldset] ||= ActiveModel::Serializer::Fieldset.new(fields_to_fieldset(instance_options.delete(:fields)))
|
9
|
+
end
|
10
|
+
|
11
|
+
def serializable_hash(options = nil)
|
12
|
+
options = serialization_options(options.dup)
|
13
|
+
options[:fields] ||= instance_options[:fields]
|
14
|
+
serialized_hash = serializer.serializable_hash(instance_options, options, self)
|
15
|
+
|
16
|
+
self.class.transform_key_casing!(serialized_hash, instance_options)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def fields_to_fieldset(fields)
|
22
|
+
return fields if fields.nil?
|
23
|
+
resource_fields = []
|
24
|
+
relationship_fields = {}
|
25
|
+
fields.each do |field|
|
26
|
+
case field
|
27
|
+
when Symbol, String then resource_fields << field
|
28
|
+
when Hash then relationship_fields.merge!(field)
|
29
|
+
else fail ArgumentError, "Unknown conversion of fields to fieldset: '#{field.inspect}' in '#{fields.inspect}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
relationship_fields.merge!(serializer.json_key.to_sym => resource_fields)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'case_transform'
|
4
|
+
|
5
|
+
module ActiveModelSerializers
|
6
|
+
module Adapter
|
7
|
+
class Base
|
8
|
+
# Automatically register adapters when subclassing
|
9
|
+
def self.inherited(subclass)
|
10
|
+
ActiveModelSerializers::Adapter.register(subclass)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Sets the default transform for the adapter.
|
14
|
+
#
|
15
|
+
# @return [Symbol] the default transform for the adapter
|
16
|
+
def self.default_key_transform
|
17
|
+
:unaltered
|
18
|
+
end
|
19
|
+
|
20
|
+
# Determines the transform to use in order of precedence:
|
21
|
+
# adapter option, global config, adapter default.
|
22
|
+
#
|
23
|
+
# @param options [Object]
|
24
|
+
# @return [Symbol] the transform to use
|
25
|
+
def self.transform(options)
|
26
|
+
return options[:key_transform] if options && options[:key_transform]
|
27
|
+
ActiveModelSerializers.config.key_transform || default_key_transform
|
28
|
+
end
|
29
|
+
|
30
|
+
# Transforms the casing of the supplied value.
|
31
|
+
#
|
32
|
+
# @param value [Object] the value to be transformed
|
33
|
+
# @param options [Object] serializable resource options
|
34
|
+
# @return [Symbol] the default transform for the adapter
|
35
|
+
def self.transform_key_casing!(value, options)
|
36
|
+
CaseTransform.send(transform(options), value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.cache_key
|
40
|
+
@cache_key ||= ActiveModelSerializers::Adapter.registered_name(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.fragment_cache(cached_hash, non_cached_hash)
|
44
|
+
non_cached_hash.merge cached_hash
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :serializer, :instance_options
|
48
|
+
|
49
|
+
def initialize(serializer, options = {})
|
50
|
+
@serializer = serializer
|
51
|
+
@instance_options = options
|
52
|
+
end
|
53
|
+
|
54
|
+
# Subclasses that implement this method must first call
|
55
|
+
# options = serialization_options(options)
|
56
|
+
def serializable_hash(_options = nil)
|
57
|
+
fail NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
58
|
+
end
|
59
|
+
|
60
|
+
def as_json(options = nil)
|
61
|
+
serializable_hash(options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def cache_key
|
65
|
+
self.class.cache_key
|
66
|
+
end
|
67
|
+
|
68
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
69
|
+
self.class.fragment_cache(cached_hash, non_cached_hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# see https://github.com/rails-api/active_model_serializers/pull/965
|
75
|
+
# When <tt>options</tt> is +nil+, sets it to +{}+
|
76
|
+
def serialization_options(options)
|
77
|
+
options ||= {} # rubocop:disable Lint/UselessAssignment
|
78
|
+
end
|
79
|
+
|
80
|
+
def root
|
81
|
+
serializer.json_key.to_sym if serializer.json_key
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModelSerializers
|
4
|
+
module Adapter
|
5
|
+
class Json < Base
|
6
|
+
def serializable_hash(options = nil)
|
7
|
+
options = serialization_options(options)
|
8
|
+
serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) }
|
9
|
+
serialized_hash[meta_key] = meta unless meta.blank?
|
10
|
+
|
11
|
+
self.class.transform_key_casing!(serialized_hash, instance_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def meta
|
15
|
+
instance_options.fetch(:meta, nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def meta_key
|
19
|
+
instance_options.fetch(:meta_key, 'meta'.freeze)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModelSerializers
|
4
|
+
module Adapter
|
5
|
+
class JsonApi
|
6
|
+
# NOTE(Experimental):
|
7
|
+
# This is an experimental feature. Both the interface and internals could be subject
|
8
|
+
# to changes.
|
9
|
+
module Deserialization
|
10
|
+
InvalidDocument = Class.new(ArgumentError)
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# Transform a JSON API document, containing a single data object,
|
15
|
+
# into a hash that is ready for ActiveRecord::Base.new() and such.
|
16
|
+
# Raises InvalidDocument if the payload is not properly formatted.
|
17
|
+
#
|
18
|
+
# @param [Hash|ActionController::Parameters] document
|
19
|
+
# @param [Hash] options
|
20
|
+
# only: Array of symbols of whitelisted fields.
|
21
|
+
# except: Array of symbols of blacklisted fields.
|
22
|
+
# keys: Hash of translated keys (e.g. :author => :user).
|
23
|
+
# polymorphic: Array of symbols of polymorphic fields.
|
24
|
+
# @return [Hash]
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# document = {
|
28
|
+
# data: {
|
29
|
+
# id: 1,
|
30
|
+
# type: 'post',
|
31
|
+
# attributes: {
|
32
|
+
# title: 'Title 1',
|
33
|
+
# date: '2015-12-20'
|
34
|
+
# },
|
35
|
+
# associations: {
|
36
|
+
# author: {
|
37
|
+
# data: {
|
38
|
+
# type: 'user',
|
39
|
+
# id: 2
|
40
|
+
# }
|
41
|
+
# },
|
42
|
+
# second_author: {
|
43
|
+
# data: nil
|
44
|
+
# },
|
45
|
+
# comments: {
|
46
|
+
# data: [{
|
47
|
+
# type: 'comment',
|
48
|
+
# id: 3
|
49
|
+
# },{
|
50
|
+
# type: 'comment',
|
51
|
+
# id: 4
|
52
|
+
# }]
|
53
|
+
# }
|
54
|
+
# }
|
55
|
+
# }
|
56
|
+
# }
|
57
|
+
#
|
58
|
+
# parse(document) #=>
|
59
|
+
# # {
|
60
|
+
# # title: 'Title 1',
|
61
|
+
# # date: '2015-12-20',
|
62
|
+
# # author_id: 2,
|
63
|
+
# # second_author_id: nil
|
64
|
+
# # comment_ids: [3, 4]
|
65
|
+
# # }
|
66
|
+
#
|
67
|
+
# parse(document, only: [:title, :date, :author],
|
68
|
+
# keys: { date: :published_at },
|
69
|
+
# polymorphic: [:author]) #=>
|
70
|
+
# # {
|
71
|
+
# # title: 'Title 1',
|
72
|
+
# # published_at: '2015-12-20',
|
73
|
+
# # author_id: '2',
|
74
|
+
# # author_type: 'people'
|
75
|
+
# # }
|
76
|
+
#
|
77
|
+
def parse!(document, options = {})
|
78
|
+
parse(document, options) do |invalid_payload, reason|
|
79
|
+
fail InvalidDocument, "Invalid payload (#{reason}): #{invalid_payload}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Same as parse!, but returns an empty hash instead of raising InvalidDocument
|
84
|
+
# on invalid payloads.
|
85
|
+
def parse(document, options = {})
|
86
|
+
document = document.dup.permit!.to_h if document.is_a?(ActionController::Parameters)
|
87
|
+
|
88
|
+
validate_payload(document) do |invalid_document, reason|
|
89
|
+
yield invalid_document, reason if block_given?
|
90
|
+
return {}
|
91
|
+
end
|
92
|
+
|
93
|
+
primary_data = document['data']
|
94
|
+
attributes = primary_data['attributes'] || {}
|
95
|
+
attributes['id'] = primary_data['id'] if primary_data['id']
|
96
|
+
relationships = primary_data['relationships'] || {}
|
97
|
+
|
98
|
+
filter_fields(attributes, options)
|
99
|
+
filter_fields(relationships, options)
|
100
|
+
|
101
|
+
hash = {}
|
102
|
+
hash.merge!(parse_attributes(attributes, options))
|
103
|
+
hash.merge!(parse_relationships(relationships, options))
|
104
|
+
|
105
|
+
hash
|
106
|
+
end
|
107
|
+
|
108
|
+
# Checks whether a payload is compliant with the JSON API spec.
|
109
|
+
#
|
110
|
+
# @api private
|
111
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
112
|
+
def validate_payload(payload)
|
113
|
+
unless payload.is_a?(Hash)
|
114
|
+
yield payload, 'Expected hash'
|
115
|
+
return
|
116
|
+
end
|
117
|
+
|
118
|
+
primary_data = payload['data']
|
119
|
+
unless primary_data.is_a?(Hash)
|
120
|
+
yield payload, { data: 'Expected hash' }
|
121
|
+
return
|
122
|
+
end
|
123
|
+
|
124
|
+
attributes = primary_data['attributes'] || {}
|
125
|
+
unless attributes.is_a?(Hash)
|
126
|
+
yield payload, { data: { attributes: 'Expected hash or nil' } }
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
relationships = primary_data['relationships'] || {}
|
131
|
+
unless relationships.is_a?(Hash)
|
132
|
+
yield payload, { data: { relationships: 'Expected hash or nil' } }
|
133
|
+
return
|
134
|
+
end
|
135
|
+
|
136
|
+
relationships.each do |(key, value)|
|
137
|
+
unless value.is_a?(Hash) && value.key?('data')
|
138
|
+
yield payload, { data: { relationships: { key => 'Expected hash with :data key' } } }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
143
|
+
|
144
|
+
# @api private
|
145
|
+
def filter_fields(fields, options)
|
146
|
+
if (only = options[:only])
|
147
|
+
fields.slice!(*Array(only).map(&:to_s))
|
148
|
+
elsif (except = options[:except])
|
149
|
+
fields.except!(*Array(except).map(&:to_s))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# @api private
|
154
|
+
def field_key(field, options)
|
155
|
+
(options[:keys] || {}).fetch(field.to_sym, field).to_sym
|
156
|
+
end
|
157
|
+
|
158
|
+
# @api private
|
159
|
+
def parse_attributes(attributes, options)
|
160
|
+
transform_keys(attributes, options)
|
161
|
+
.map { |(k, v)| { field_key(k, options) => v } }
|
162
|
+
.reduce({}, :merge)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Given an association name, and a relationship data attribute, build a hash
|
166
|
+
# mapping the corresponding ActiveRecord attribute to the corresponding value.
|
167
|
+
#
|
168
|
+
# @example
|
169
|
+
# parse_relationship(:comments, [{ 'id' => '1', 'type' => 'comments' },
|
170
|
+
# { 'id' => '2', 'type' => 'comments' }],
|
171
|
+
# {})
|
172
|
+
# # => { :comment_ids => ['1', '2'] }
|
173
|
+
# parse_relationship(:author, { 'id' => '1', 'type' => 'users' }, {})
|
174
|
+
# # => { :author_id => '1' }
|
175
|
+
# parse_relationship(:author, nil, {})
|
176
|
+
# # => { :author_id => nil }
|
177
|
+
# @param [Symbol] assoc_name
|
178
|
+
# @param [Hash] assoc_data
|
179
|
+
# @param [Hash] options
|
180
|
+
# @return [Hash{Symbol, Object}]
|
181
|
+
#
|
182
|
+
# @api private
|
183
|
+
def parse_relationship(assoc_name, assoc_data, options)
|
184
|
+
prefix_key = field_key(assoc_name, options).to_s.singularize
|
185
|
+
hash =
|
186
|
+
if assoc_data.is_a?(Array)
|
187
|
+
{ "#{prefix_key}_ids".to_sym => assoc_data.map { |ri| ri['id'] } }
|
188
|
+
else
|
189
|
+
{ "#{prefix_key}_id".to_sym => assoc_data ? assoc_data['id'] : nil }
|
190
|
+
end
|
191
|
+
|
192
|
+
polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym)
|
193
|
+
if polymorphic
|
194
|
+
hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'].classify : nil
|
195
|
+
end
|
196
|
+
|
197
|
+
hash
|
198
|
+
end
|
199
|
+
|
200
|
+
# @api private
|
201
|
+
def parse_relationships(relationships, options)
|
202
|
+
transform_keys(relationships, options)
|
203
|
+
.map { |(k, v)| parse_relationship(k, v['data'], options) }
|
204
|
+
.reduce({}, :merge)
|
205
|
+
end
|
206
|
+
|
207
|
+
# @api private
|
208
|
+
def transform_keys(hash, options)
|
209
|
+
transform = options[:key_transform] || :underscore
|
210
|
+
CaseTransform.send(transform, hash)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModelSerializers
|
4
|
+
module Adapter
|
5
|
+
class JsonApi < Base
|
6
|
+
module Error
|
7
|
+
# rubocop:disable Style/AsciiComments
|
8
|
+
UnknownSourceTypeError = Class.new(ArgumentError)
|
9
|
+
|
10
|
+
# Builds a JSON API Errors Object
|
11
|
+
# {http://jsonapi.org/format/#errors JSON API Errors}
|
12
|
+
#
|
13
|
+
# @param [ActiveModel::Serializer::ErrorSerializer] error_serializer
|
14
|
+
# @return [Array<Symbol, Array<String>>] i.e. attribute_name, [attribute_errors]
|
15
|
+
def self.resource_errors(error_serializer, options)
|
16
|
+
error_serializer.as_json.flat_map do |attribute_name, attribute_errors|
|
17
|
+
attribute_name = JsonApi.send(:transform_key_casing!, attribute_name,
|
18
|
+
options)
|
19
|
+
attribute_error_objects(attribute_name, attribute_errors)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# definition:
|
24
|
+
# JSON Object
|
25
|
+
#
|
26
|
+
# properties:
|
27
|
+
# ☐ id : String
|
28
|
+
# ☐ status : String
|
29
|
+
# ☐ code : String
|
30
|
+
# ☐ title : String
|
31
|
+
# ☑ detail : String
|
32
|
+
# ☐ links
|
33
|
+
# ☐ meta
|
34
|
+
# ☑ error_source
|
35
|
+
#
|
36
|
+
# description:
|
37
|
+
# id : A unique identifier for this particular occurrence of the problem.
|
38
|
+
# status : The HTTP status code applicable to this problem, expressed as a string value
|
39
|
+
# code : An application-specific error code, expressed as a string value.
|
40
|
+
# title : A short, human-readable summary of the problem. It **SHOULD NOT** change from
|
41
|
+
# occurrence to occurrence of the problem, except for purposes of localization.
|
42
|
+
# detail : A human-readable explanation specific to this occurrence of the problem.
|
43
|
+
# structure:
|
44
|
+
# {
|
45
|
+
# title: 'SystemFailure',
|
46
|
+
# detail: 'something went terribly wrong',
|
47
|
+
# status: '500'
|
48
|
+
# }.merge!(errorSource)
|
49
|
+
def self.attribute_error_objects(attribute_name, attribute_errors)
|
50
|
+
attribute_errors.map do |attribute_error|
|
51
|
+
{
|
52
|
+
source: error_source(:pointer, attribute_name),
|
53
|
+
detail: attribute_error
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# errorSource
|
59
|
+
# description:
|
60
|
+
# oneOf
|
61
|
+
# ☑ pointer : String
|
62
|
+
# ☑ parameter : String
|
63
|
+
#
|
64
|
+
# description:
|
65
|
+
# pointer: A JSON Pointer RFC6901 to the associated entity in the request document e.g. "/data"
|
66
|
+
# for a primary data object, or "/data/attributes/title" for a specific attribute.
|
67
|
+
# https://tools.ietf.org/html/rfc6901
|
68
|
+
#
|
69
|
+
# parameter: A string indicating which query parameter caused the error
|
70
|
+
# structure:
|
71
|
+
# if is_attribute?
|
72
|
+
# {
|
73
|
+
# pointer: '/data/attributes/red-button'
|
74
|
+
# }
|
75
|
+
# else
|
76
|
+
# {
|
77
|
+
# parameter: 'pres'
|
78
|
+
# }
|
79
|
+
# end
|
80
|
+
def self.error_source(source_type, attribute_name)
|
81
|
+
case source_type
|
82
|
+
when :pointer
|
83
|
+
{
|
84
|
+
pointer: ActiveModelSerializers::JsonPointer.new(:attribute, attribute_name)
|
85
|
+
}
|
86
|
+
when :parameter
|
87
|
+
{
|
88
|
+
parameter: attribute_name
|
89
|
+
}
|
90
|
+
else
|
91
|
+
fail UnknownSourceTypeError, "Unknown source type '#{source_type}' for attribute_name '#{attribute_name}'"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
# rubocop:enable Style/AsciiComments
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModelSerializers
|
4
|
+
module Adapter
|
5
|
+
class JsonApi < Base
|
6
|
+
# {http://jsonapi.org/format/#document-jsonapi-object Jsonapi Object}
|
7
|
+
|
8
|
+
# toplevel_jsonapi
|
9
|
+
# definition:
|
10
|
+
# JSON Object
|
11
|
+
#
|
12
|
+
# properties:
|
13
|
+
# version : String
|
14
|
+
# meta
|
15
|
+
#
|
16
|
+
# description:
|
17
|
+
# An object describing the server's implementation
|
18
|
+
# structure:
|
19
|
+
# {
|
20
|
+
# version: ActiveModelSerializers.config.jsonapi_version,
|
21
|
+
# meta: ActiveModelSerializers.config.jsonapi_toplevel_meta
|
22
|
+
# }.reject! { |_, v| v.blank? }
|
23
|
+
# prs:
|
24
|
+
# https://github.com/rails-api/active_model_serializers/pull/1050
|
25
|
+
module Jsonapi
|
26
|
+
module_function
|
27
|
+
|
28
|
+
def add!(hash)
|
29
|
+
hash.merge!(object) if include_object?
|
30
|
+
end
|
31
|
+
|
32
|
+
def include_object?
|
33
|
+
ActiveModelSerializers.config.jsonapi_include_toplevel_object
|
34
|
+
end
|
35
|
+
|
36
|
+
# TODO: see if we can cache this
|
37
|
+
def object
|
38
|
+
object = {
|
39
|
+
jsonapi: {
|
40
|
+
version: ActiveModelSerializers.config.jsonapi_version,
|
41
|
+
meta: ActiveModelSerializers.config.jsonapi_toplevel_meta
|
42
|
+
}
|
43
|
+
}
|
44
|
+
object[:jsonapi].reject! { |_, v| v.blank? }
|
45
|
+
|
46
|
+
object
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModelSerializers
|
4
|
+
module Adapter
|
5
|
+
class JsonApi
|
6
|
+
# link
|
7
|
+
# definition:
|
8
|
+
# oneOf
|
9
|
+
# linkString
|
10
|
+
# linkObject
|
11
|
+
#
|
12
|
+
# description:
|
13
|
+
# A link **MUST** be represented as either: a string containing the link's URL or a link
|
14
|
+
# object."
|
15
|
+
# structure:
|
16
|
+
# if href?
|
17
|
+
# linkString
|
18
|
+
# else
|
19
|
+
# linkObject
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# linkString
|
23
|
+
# definition:
|
24
|
+
# URI
|
25
|
+
#
|
26
|
+
# description:
|
27
|
+
# A string containing the link's URL.
|
28
|
+
# structure:
|
29
|
+
# 'http://example.com/link-string'
|
30
|
+
#
|
31
|
+
# linkObject
|
32
|
+
# definition:
|
33
|
+
# JSON Object
|
34
|
+
#
|
35
|
+
# properties:
|
36
|
+
# href (required) : URI
|
37
|
+
# meta
|
38
|
+
# structure:
|
39
|
+
# {
|
40
|
+
# href: 'http://example.com/link-object',
|
41
|
+
# meta: meta,
|
42
|
+
# }.reject! {|_,v| v.nil? }
|
43
|
+
class Link
|
44
|
+
include SerializationContext::UrlHelpers
|
45
|
+
|
46
|
+
def initialize(serializer, value)
|
47
|
+
@_routes ||= nil # handles warning
|
48
|
+
# actionpack-4.0.13/lib/action_dispatch/routing/route_set.rb:417: warning: instance variable @_routes not initialized
|
49
|
+
@object = serializer.object
|
50
|
+
@scope = serializer.scope
|
51
|
+
# Use the return value of the block unless it is nil.
|
52
|
+
if value.respond_to?(:call)
|
53
|
+
@value = instance_eval(&value)
|
54
|
+
else
|
55
|
+
@value = value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def href(value)
|
60
|
+
@href = value
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def meta(value)
|
65
|
+
@meta = value
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def as_json
|
70
|
+
return @value if @value
|
71
|
+
|
72
|
+
hash = {}
|
73
|
+
hash[:href] = @href if defined?(@href)
|
74
|
+
hash[:meta] = @meta if defined?(@meta)
|
75
|
+
|
76
|
+
hash.any? ? hash : nil
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
attr_reader :object, :scope
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModelSerializers
|
4
|
+
module Adapter
|
5
|
+
class JsonApi
|
6
|
+
# meta
|
7
|
+
# definition:
|
8
|
+
# JSON Object
|
9
|
+
#
|
10
|
+
# description:
|
11
|
+
# Non-standard meta-information that can not be represented as an attribute or relationship.
|
12
|
+
# structure:
|
13
|
+
# {
|
14
|
+
# attitude: 'adjustable'
|
15
|
+
# }
|
16
|
+
class Meta
|
17
|
+
def initialize(serializer)
|
18
|
+
@object = serializer.object
|
19
|
+
@scope = serializer.scope
|
20
|
+
|
21
|
+
# Use the return value of the block unless it is nil.
|
22
|
+
if serializer._meta.respond_to?(:call)
|
23
|
+
@value = instance_eval(&serializer._meta)
|
24
|
+
else
|
25
|
+
@value = serializer._meta
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_json
|
30
|
+
@value
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
attr_reader :object, :scope
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|