Skip to content

Commit bd55631

Browse files
author
Lee Richmond
committed
Refactor; enhance tests; add 'code'
Addresses #11
1 parent ec5eaaf commit bd55631

File tree

5 files changed

+365
-205
lines changed

5 files changed

+365
-205
lines changed

gemfiles/rails_4.gemfile.lock

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
PATH
2-
remote: ../
2+
remote: ..
33
specs:
4-
jsonapi_errorable (0.1.1)
5-
active_model_serializers (~> 0.10)
6-
rails (>= 4.1, < 6)
4+
jsonapi_errorable (0.7.0)
5+
jsonapi-serializable (~> 0.1)
76

87
GEM
98
remote: https://rubygems.org/
@@ -27,11 +26,6 @@ GEM
2726
erubis (~> 2.7.0)
2827
rails-dom-testing (~> 1.0, >= 1.0.5)
2928
rails-html-sanitizer (~> 1.0, >= 1.0.2)
30-
active_model_serializers (0.10.2)
31-
actionpack (>= 4.1, < 6)
32-
activemodel (>= 4.1, < 6)
33-
jsonapi (~> 0.1.1.beta2)
34-
railties (>= 4.1, < 6)
3529
activejob (4.2.6)
3630
activesupport (= 4.2.6)
3731
globalid (>= 0.3.0)
@@ -63,8 +57,9 @@ GEM
6357
activesupport (>= 4.1.0)
6458
i18n (0.7.0)
6559
json (1.8.3)
66-
jsonapi (0.1.1.beta2)
67-
json (~> 1.8)
60+
jsonapi-renderer (0.2.0)
61+
jsonapi-serializable (0.3.0)
62+
jsonapi-renderer (~> 0.2.0)
6863
jsonapi_spec_helpers (0.2.0)
6964
loofah (2.0.3)
7065
nokogiri (>= 1.5.9)
@@ -162,4 +157,4 @@ DEPENDENCIES
162157
sqlite3
163158

164159
BUNDLED WITH
165-
1.12.5
160+
1.15.4

gemfiles/rails_5.gemfile.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: ..
33
specs:
4-
jsonapi_errorable (0.6.2)
4+
jsonapi_errorable (0.7.0)
55
jsonapi-serializable (~> 0.1)
66

77
GEM
@@ -58,9 +58,9 @@ GEM
5858
globalid (0.3.7)
5959
activesupport (>= 4.1.0)
6060
i18n (0.7.0)
61-
jsonapi-renderer (0.1.2)
62-
jsonapi-serializable (0.1.3)
63-
jsonapi-renderer (~> 0.1)
61+
jsonapi-renderer (0.2.0)
62+
jsonapi-serializable (0.3.0)
63+
jsonapi-renderer (~> 0.2.0)
6464
jsonapi_spec_helpers (0.2.0)
6565
loofah (2.0.3)
6666
nokogiri (>= 1.5.9)
@@ -161,4 +161,4 @@ DEPENDENCIES
161161
sqlite3
162162

163163
BUNDLED WITH
164-
1.15.0
164+
1.15.4

jsonapi_errorable.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
2020

2121
spec.add_dependency 'jsonapi-serializable', '~> 0.1'
2222

23+
# Rails is added in Appraisals
2324
spec.add_development_dependency "bundler", "~> 1.11"
2425
spec.add_development_dependency "rake", "~> 10.0"
2526
spec.add_development_dependency "rspec-rails", "~> 3.0"

lib/jsonapi_errorable/serializers/validation.rb

Lines changed: 100 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,85 +3,146 @@ module Serializers
33
class Validation
44
attr_reader :object
55

6-
def initialize(object, relationship_params = {}, relationship_message = {})
6+
def initialize(object, relationship_payloads = {}, relationship_meta = {})
77
@object = object
8-
@relationship_params = relationship_params || {}
9-
@relationship_message = relationship_message
8+
@relationship_payloads = relationship_payloads
9+
@relationship_meta = relationship_meta
1010
end
1111

12-
def errors
13-
return [] unless object.respond_to?(:errors)
14-
15-
all_errors = object.errors.to_hash.map do |attribute, messages|
16-
messages.map do |message|
17-
meta = { attribute: attribute, message: message }.merge(@relationship_message)
18-
meta = { relationship: meta } if @relationship_message.present?
19-
20-
detail = object.errors.full_message(attribute, message)
21-
detail = message if attribute.to_s.downcase == 'base'
22-
23-
{
12+
def attribute_errors
13+
[].tap do |errors|
14+
each_error do |attribute, message, code|
15+
error = {
2416
code: 'unprocessable_entity',
2517
status: '422',
2618
title: 'Validation Error',
27-
detail: detail,
19+
detail: detail_for(attribute, message),
2820
source: { pointer: pointer_for(object, attribute) },
29-
meta: meta
21+
meta: meta_for(attribute, message, code, @relationship_meta)
3022
}
23+
24+
errors << error
3125
end
32-
end.flatten
33-
all_errors << relationship_errors(@relationship_params)
34-
all_errors.flatten!
35-
all_errors.compact!
26+
end
27+
end
28+
29+
def errors
30+
return [] unless object.respond_to?(:errors)
31+
32+
all_errors = attribute_errors
33+
all_errors |= relationship_errors(@relationship_payloads)
3634
all_errors
3735
end
3836

37+
private
38+
39+
def each_error
40+
object.errors.messages.each_pair do |attribute, messages|
41+
details = if Rails::VERSION::MAJOR >= 5
42+
object.errors.details.find { |k,v| k == attribute }[1]
43+
end
44+
45+
messages.each_with_index do |message, index|
46+
code = details[index][:error] if details
47+
yield attribute, message, code
48+
end
49+
end
50+
end
51+
3952
def relationship?(name)
40-
return false unless activemodel?
53+
relationship_names = []
54+
if activerecord?
55+
relationship_names = object.class
56+
.reflect_on_all_associations.map(&:name)
57+
elsif object.respond_to?(:relationship_names)
58+
relationship_names = object.relationship_names
59+
end
4160

42-
relation_names = object.class.reflect_on_all_associations.map(&:name)
43-
relation_names.include?(name)
61+
relationship_names.include?(name)
4462
end
4563

4664
def attribute?(name)
4765
object.respond_to?(name)
4866
end
4967

50-
private
68+
def meta_for(attribute, message, code, relationship_meta)
69+
meta = {
70+
attribute: attribute,
71+
message: message
72+
}
73+
meta.merge!(code: code) if Rails::VERSION::MAJOR >= 5
74+
75+
unless relationship_meta.empty?
76+
meta = {
77+
relationship: meta.merge(relationship_meta)
78+
}
79+
end
80+
81+
meta
82+
end
5183

84+
def detail_for(attribute, message)
85+
detail = object.errors.full_message(attribute, message)
86+
detail = message if attribute.to_s.downcase == 'base'
87+
detail
88+
end
89+
90+
# @richmolj: Keeping this to support ember-data, but I hate the concept.
5291
def pointer_for(object, name)
5392
if relationship?(name)
5493
"/data/relationships/#{name}"
5594
elsif attribute?(name)
5695
"/data/attributes/#{name}"
96+
elsif name == :base
97+
nil
5798
else
5899
# Probably a nested relation, like post.comments
59100
"/data/relationships/#{name}"
60101
end
61102
end
62103

63-
def activemodel?
104+
def activerecord?
64105
object.class.respond_to?(:reflect_on_all_associations)
65106
end
66107

67-
def relationship_errors(relationship_params)
68-
errors = []
108+
def traverse_relationships(relationship_params)
109+
return unless relationship_params
110+
69111
relationship_params.each_pair do |name, payload|
70-
related = Array(@object.send(name))
71-
related.each do |r|
112+
relationship_objects = Array(@object.send(name))
113+
114+
relationship_objects.each do |relationship_object|
115+
related_payload = payload
72116
if payload.is_a?(Array)
73-
related_payload = payload.find { |p| p[:meta][:temp_id] === r.instance_variable_get(:@_jsonapi_temp_id) || p[:meta][:id] == r.id }
74-
else
75-
related_payload = payload
117+
related_payload = payload.find do |p|
118+
temp_id = relationship_object
119+
.instance_variable_get(:@_jsonapi_temp_id)
120+
p[:meta][:temp_id] === temp_id ||
121+
p[:meta][:id] == relationship_object.id.to_s
122+
end
76123
end
77-
relationship_message = {
78-
name: name,
79-
id: r.id,
80-
:'temp-id' => r.instance_variable_get(:@_jsonapi_temp_id)
81-
}
82124

83-
errors << Validation.new(r, related_payload[:relationships], relationship_message).errors
125+
yield name, relationship_object, related_payload
126+
traverse_relationships(related_payload[:relationships])
127+
end
128+
end
129+
end
130+
131+
def relationship_errors(relationship_payloads)
132+
errors = []
133+
traverse_relationships(relationship_payloads) do |name, model, payload|
134+
meta = {}.tap do |hash|
135+
hash[:name] = name
136+
hash[:type] = payload[:meta][:jsonapi_type]
137+
if temp_id = model.instance_variable_get(:@_jsonapi_temp_id)
138+
hash[:'temp-id'] = temp_id
139+
else
140+
hash[:id] = model.id
141+
end
84142
end
143+
144+
serializer = self.class.new(model, payload[:relationships], meta)
145+
errors |= serializer.errors
85146
end
86147
errors
87148
end

0 commit comments

Comments
 (0)