Skip to content

Commit 63a5b11

Browse files
authored
Merge pull request #22 from richmolj/master
Improve error handling
2 parents 38310ba + 713bd28 commit 63a5b11

File tree

2 files changed

+77
-12
lines changed

2 files changed

+77
-12
lines changed

src/util/validation-errors.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Model from '../model';
2+
import { camelize } from './string'
23

34
export default class ValidationErrors {
45
model: Model;
@@ -32,7 +33,13 @@ export default class ValidationErrors {
3233
}
3334

3435
private _processResource(errorsAccumulator: object, meta: Object) {
35-
errorsAccumulator[meta['attribute']] = meta['message'];
36+
let attribute = meta['attribute']
37+
38+
if (this.model.klass.camelizeKeys) {
39+
attribute = camelize(attribute)
40+
}
41+
42+
errorsAccumulator[attribute] = meta['message'];
3643
}
3744

3845
private _processRelationship(model: Model, meta: Object) {
@@ -48,8 +55,18 @@ export default class ValidationErrors {
4855
} else {
4956
let relatedAccumulator = {}
5057
this._processResource(relatedAccumulator, meta);
51-
relatedObject.errors = relatedAccumulator
52-
}
5358

59+
// make sure to assign a new error object, instead of mutating
60+
// the existing one, otherwise js frameworks with object tracking
61+
// won't be able to keep up. Validate vue.js when changing this code:
62+
let newErrs = {}
63+
Object.keys(relatedObject.errors).forEach((key) => {
64+
newErrs[key] = relatedObject.errors[key]
65+
});
66+
Object.keys(relatedAccumulator).forEach((key) => {
67+
newErrs[key] = relatedAccumulator[key]
68+
});
69+
relatedObject.errors = newErrs
70+
}
5471
}
5572
}

test/integration/validations-test.ts

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { expect, sinon, fetchMock } from '../test-helper';
22
import { Author, Book, Genre } from '../fixtures';
33
import uuid from '../../src/util/uuid';
44

5-
let serverResponse;
6-
75
const resetMocks = function() {
86
fetchMock.restore();
97

@@ -61,15 +59,31 @@ const resetMocks = function() {
6159
}
6260
}
6361
}
62+
},
63+
{
64+
code: 'unprocessable_entity',
65+
status: '422',
66+
title: 'Validation Error',
67+
detail: 'base some error',
68+
meta: {
69+
relationship: {
70+
name: 'books',
71+
type: 'books',
72+
['temp-id']: 'abc1',
73+
relationship: {
74+
name: 'genre',
75+
type: 'genres',
76+
id: '1',
77+
attribute: 'base',
78+
message: 'some error'
79+
}
80+
}
81+
}
6482
}
6583
]
6684
}
6785
}
6886
});
69-
70-
fetchMock.post('http://example.com/api/v1/people', function(url, payload) {
71-
return serverResponse;
72-
});
7387
}
7488

7589
let instance;
@@ -97,19 +111,50 @@ describe('validations', function() {
97111
uuid.generate['restore']();
98112
});
99113

100-
// todo on next save, remove errs
101114
it('applies errors to the instance', function(done) {
102115
instance.save({ with: { books: 'genre' }}).then((success) => {
103116
expect(instance.isPersisted()).to.eq(false);
104117
expect(success).to.eq(false);
105118
expect(instance.errors).to.deep.equal({
106-
first_name: 'cannot be blank',
107-
last_name: 'cannot be blank'
119+
firstName: 'cannot be blank',
120+
lastName: 'cannot be blank'
108121
});
109122
done();
110123
});
111124
});
112125

126+
describe('when camelizeKeys is false', function() {
127+
beforeEach(function() {
128+
instance.klass.camelizeKeys = false
129+
});
130+
131+
afterEach(function() {
132+
instance.klass.camelizeKeys = true
133+
});
134+
135+
it('does not camelize the error keys', function() {
136+
instance.save({ with: { books: 'genre' }}).then((success) => {
137+
expect(instance.errors).to.deep.equal({
138+
first_name: 'cannot be blank',
139+
last_name: 'cannot be blank'
140+
});
141+
});
142+
});
143+
});
144+
145+
it('clears errors on save', function(done) {
146+
fetchMock.restore()
147+
fetchMock.mock({
148+
matcher: '*',
149+
response: { data: { id: '1', type: 'employees'} }
150+
});
151+
instance.errors = { foo: 'bar' }
152+
instance.save().then(() => {
153+
expect(instance.errors).to.deep.eq({})
154+
done()
155+
});
156+
});
157+
113158
it('instantiates a new error object instance after save', function(done) {
114159
let originalErrors = instance.errors = {foo: 'bar'};
115160
let result = instance.save({ with: { books: 'genre' }});
@@ -151,8 +196,11 @@ describe('validations', function() {
151196
instance.save({ with: { books: 'genre' }}).then((success) => {
152197
expect(instance.isPersisted()).to.eq(false);
153198
expect(success).to.eq(false);
199+
200+
// note we're validating multiple properties
154201
expect(instance.books[0].genre.errors).to.deep.equal({
155202
name: 'cannot be blank',
203+
base: 'some error'
156204
});
157205
done();
158206
});

0 commit comments

Comments
 (0)