Skip to content

Commit 3d98637

Browse files
committed
Collapse JSON API success/failure documents in one adapter
Idea per remear (Ben Mills) in the slack: https://amserializers.slack.com/archives/general/p1455140474000171 remear: just so i understand, the adapter in `render json: resource, status: 422, adapter: 'json_api/error', serializer: ActiveModel::Serializer::ErrorSerializer` is a different one than, say what i’ve specified in a base serializer with `ActiveModel::Serializer.config.adapter = :json_api`. correct? and a followup question of, why not same adapter but different serializer? me: With the way the code is written now, it might be possible to not require a special jsonapi adapter. However, the behavior is pretty different from the jsonapi adapter. this first draft of the PR had it automatically set the adapter if there were errors. since that requires more discussion, I took a step back and made it explicit for this PR If I were to re-use the json api adapter and remove the error one, it think the serializable hash method would look like ``` def serializable_hash(options = nil) return { errors: JsonApi::Error.collection_errors } if serializer.is_a?(ErrorsSerializer) return { errors: JsonApi::Error.resource_errors(serializer) } if serializer.is_a?(ErrorSerializer) options ||= {} ``` I suppose it could be something more duckish like ``` def serializable_hash(options = nil) if serializer.errors? # object.errors.any? || object.any? {|o| o.errors.any? } JsonApi::Error.new(serializer).serializable_hash else # etc ```
1 parent 96107c5 commit 3d98637

File tree

11 files changed

+60
-44
lines changed

11 files changed

+60
-44
lines changed

docs/jsonapi/errors.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
# JSON API Errors
44

5-
Rendering error documents requires specifying the serializer and the adapter:
5+
Rendering error documents requires specifying the error serializer(s):
66

7-
- `adapter: :'json_api/error'`
87
- Serializer:
98
- For a single resource: `serializer: ActiveModel::Serializer::ErrorSerializer`.
109
- For a collection: `serializer: ActiveModel::Serializer::ErrorsSerializer`, `each_serializer: ActiveModel::Serializer::ErrorSerializer`.
@@ -23,7 +22,7 @@ resource.errors.add(:name, 'cannot be nil')
2322
resource.errors.add(:name, 'must be longer')
2423
resource.errors.add(:id, 'must be a uuid')
2524

26-
render json: resource, status: 422, adapter: 'json_api/error', serializer: ActiveModel::Serializer::ErrorSerializer
25+
render json: resource, status: 422, adapter: :json_api, serializer: ActiveModel::Serializer::ErrorSerializer
2726
# #=>
2827
# { :errors =>
2928
# [
@@ -44,7 +43,7 @@ resource.errors.add(:name, 'must be awesome')
4443
serializable_resource = ActiveModel::SerializableResource.new(
4544
resource, {
4645
serializer: ActiveModel::Serializer::ErrorSerializer,
47-
adapter: 'json_api/error'
46+
adapter: :json_api
4847
})
4948
serializable_resource.as_json(options)
5049
# #=>

lib/active_model/serializer.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ def initialize(object, options = {})
118118
end
119119
end
120120

121+
def success?
122+
true
123+
end
124+
121125
# Used by adapter as resource root.
122126
def json_key
123127
root || object.class.model_name.to_s.underscore

lib/active_model/serializer/collection_serializer.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ def initialize(resources, options = {})
2222
end
2323
end
2424

25+
def success?
26+
true
27+
end
28+
2529
def json_key
2630
root || derived_root
2731
end

lib/active_model/serializer/error_serializer.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ class ActiveModel::Serializer::ErrorSerializer < ActiveModel::Serializer
33
def as_json
44
object.errors.messages
55
end
6+
7+
def success?
8+
false
9+
end
610
end

lib/active_model/serializer/errors_serializer.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require 'active_model/serializer/error_serializer'
2-
class ActiveModel::Serializer::ErrorsSerializer < ActiveModel::Serializer
2+
class ActiveModel::Serializer::ErrorsSerializer
33
include Enumerable
44
delegate :each, to: :@serializers
55
attr_reader :object, :root
@@ -13,6 +13,10 @@ def initialize(resources, options = {})
1313
end
1414
end
1515

16+
def success?
17+
false
18+
end
19+
1620
def json_key
1721
nil
1822
end

lib/active_model_serializers/adapter/json_api.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,14 @@ def initialize(serializer, options = {})
5353

5454
def serializable_hash(options = nil)
5555
options ||= {}
56+
if serializer.success?
57+
success_document(options)
58+
else
59+
failure_document
60+
end
61+
end
5662

63+
def success_document(options)
5764
is_collection = serializer.respond_to?(:each)
5865
serializers = is_collection ? serializer : [serializer]
5966
primary_data, included = resource_objects_for(serializers)
@@ -77,6 +84,28 @@ def serializable_hash(options = nil)
7784
hash
7885
end
7986

87+
# TODO: look into caching
88+
# rubocop:disable Style/AsciiComments
89+
# definition:
90+
# ☑ toplevel_errors array (required)
91+
# ☐ toplevel_meta
92+
# ☐ toplevel_jsonapi
93+
# rubocop:enable Style/AsciiComments
94+
def failure_document
95+
hash = {}
96+
# PR Please :)
97+
# ApiObjects::Jsonapi.add!(hash)
98+
99+
if serializer.respond_to?(:each)
100+
hash[:errors] = serializer.flat_map do |error_serializer|
101+
Error.resource_errors(error_serializer)
102+
end
103+
else
104+
hash[:errors] = Error.resource_errors(serializer)
105+
end
106+
hash
107+
end
108+
80109
def fragment_cache(cached_hash, non_cached_hash)
81110
root = false if instance_options.include?(:include)
82111
ActiveModelSerializers::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash)

lib/active_model_serializers/adapter/json_api/error.rb

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,13 @@
11
module ActiveModelSerializers
22
module Adapter
33
class JsonApi < Base
4-
class Error < Base
5-
UnknownSourceTypeError = Class.new(ArgumentError)
4+
module Error
65
# rubocop:disable Style/AsciiComments
7-
# TODO: look into caching
8-
9-
# definition:
10-
# ☐ toplevel_errors array (required)
11-
# ☑ toplevel_meta
12-
# ☑ toplevel_jsonapi
13-
def serializable_hash(*)
14-
hash = {}
15-
# PR Please :)
16-
# Jsonapi.add!(hash)
17-
18-
# Checking object since we're not using an ArraySerializer
19-
if serializer.object.respond_to?(:each)
20-
hash[:errors] = collection_errors
21-
else
22-
hash[:errors] = Error.resource_errors(serializer)
23-
end
24-
hash
25-
end
6+
UnknownSourceTypeError = Class.new(ArgumentError)
267

8+
# Builds a JSON API Errors Object
9+
# {http://jsonapi.org/format/#errors JSON API Errors}
10+
#
2711
# @param [ActiveModel::Serializer::ErrorSerializer]
2812
# @return [Array<Symbol, Array<String>] i.e. attribute_name, [attribute_errors]
2913
def self.resource_errors(error_serializer)
@@ -86,16 +70,6 @@ def self.error_source(source_type, attribute_name)
8670
fail UnknownSourceTypeError, "Unknown source type '#{source_type}' for attribute_name '#{attribute_name}'"
8771
end
8872
end
89-
90-
private
91-
92-
# @return [Array<#object_errors>]
93-
def collection_errors
94-
serializer.flat_map do |error_serializer|
95-
Error.resource_errors(error_serializer)
96-
end
97-
end
98-
9973
# rubocop:enable Style/AsciiComments
10074
end
10175
end

test/action_controller/json_api/errors_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def render_resource_with_errors
3030
resource.errors.add(:name, 'cannot be nil')
3131
resource.errors.add(:name, 'must be longer')
3232
resource.errors.add(:id, 'must be a uuid')
33-
render json: resource, adapter: 'json_api/error', serializer: ActiveModel::Serializer::ErrorSerializer
33+
render json: resource, adapter: :json_api, serializer: ActiveModel::Serializer::ErrorSerializer
3434
end
3535
end
3636

test/active_model_serializers/adapter_for_test.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ def test_adapter_map
102102
'null'.freeze => ActiveModelSerializers::Adapter::Null,
103103
'json'.freeze => ActiveModelSerializers::Adapter::Json,
104104
'attributes'.freeze => ActiveModelSerializers::Adapter::Attributes,
105-
'json_api'.freeze => ActiveModelSerializers::Adapter::JsonApi,
106-
'json_api/error'.freeze => ActiveModelSerializers::Adapter::JsonApi::Error
105+
'json_api'.freeze => ActiveModelSerializers::Adapter::JsonApi
107106
}
108107
actual = ActiveModelSerializers::Adapter.adapter_map
109108
assert_equal actual, expected_adapter_map
@@ -114,7 +113,6 @@ def test_adapters
114113
'attributes'.freeze,
115114
'json'.freeze,
116115
'json_api'.freeze,
117-
'json_api/error'.freeze,
118116
'null'.freeze
119117
]
120118
end

test/adapter/json_api/errors_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def setup
1313
def test_active_model_with_error
1414
options = {
1515
serializer: ActiveModel::Serializer::ErrorSerializer,
16-
adapter: :'json_api/error'
16+
adapter: :json_api
1717
}
1818

1919
@resource.errors.add(:name, 'cannot be nil')
@@ -37,7 +37,7 @@ def test_active_model_with_error
3737
def test_active_model_with_multiple_errors
3838
options = {
3939
serializer: ActiveModel::Serializer::ErrorSerializer,
40-
adapter: :'json_api/error'
40+
adapter: :json_api
4141
}
4242

4343
@resource.errors.add(:name, 'cannot be nil')

test/serializable_resource_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def test_serializable_resource_with_errors
4040
serializable_resource = ActiveModel::SerializableResource.new(
4141
resource, {
4242
serializer: ActiveModel::Serializer::ErrorSerializer,
43-
adapter: 'json_api/error'
43+
adapter: :json_api
4444
})
4545
expected_response_document =
4646
{ :errors =>
@@ -61,7 +61,7 @@ def test_serializable_resource_with_collection_containing_errors
6161
resources, {
6262
serializer: ActiveModel::Serializer::ErrorsSerializer,
6363
each_serializer: ActiveModel::Serializer::ErrorSerializer,
64-
adapter: 'json_api/error'
64+
adapter: :json_api
6565
})
6666
expected_response_document =
6767
{ :errors =>

0 commit comments

Comments
 (0)