Skip to content

Commit

Permalink
Add useFirstErrorOnly option to Model
Browse files Browse the repository at this point in the history
  • Loading branch information
rtheunissen committed Nov 2, 2017
1 parent a46e1c8 commit bf96890
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 17 deletions.
4 changes: 3 additions & 1 deletion docs/_includes/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ task2.getOption('editable'); // false
|-------------------------+------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `patch` | `Boolean` | `false` | Whether this model should perform a "patch" on update (only send attributes that have changed). |
|-------------------------+------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `patchUnchanged` | `Boolean` | `false` | Whether this model should perform a "patch" on update if no changes have been made. |
| `saveUnchanged` | `Boolean` | `true` | Whether this model should save even if no attributes have changed. If set to `false` and no changes have been made, the request will be a considered a success. |
|-------------------------+------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `useFirstErrorOnly` | `Boolean` | `false` | Whether this model should only use the first validation error it receives, rather than an array of errors. |
|-------------------------+------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `validateOnChange` | `Boolean` | `false` | Whether this model should validate an attribute after it has changed. This would only affect the errors of the changed attribute and will only be applied if the value is not blank. |
|-------------------------+------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Expand Down
2 changes: 1 addition & 1 deletion src/Structures/Base.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class Base {
// Default route parameter interpolation pattern.
routeParameterPattern: this.getDefaultRouteParameterPattern(),

//
// The HTTP status code to use for indicating a validation error.
validationErrorStatus: 422,
}
}
Expand Down
36 changes: 25 additions & 11 deletions src/Structures/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,15 @@ class Model extends Base {
// which will only send changed attributes in the request.
patch: false,

//
// Whether this model should save even if no attributes have changed
// since the last time they were synced. If set to `false` and no
// changes have been made, the request will be a considered a success.
saveUnchanged: true,

// Whether this model should only use the first validation error it
// receives, rather than an array of errors.
useFirstErrorOnly: false,

// Whether this model should validate an attribute that has changed.
// This would only affect the errors of the changed attribute and
// will only be applied if the value is not a blank string.
Expand Down Expand Up @@ -462,7 +468,7 @@ class Model extends Base {
Vue.set(this._attributes, attribute, value);

// Only emit `change` if the value has changed.
this.emit('change', {attribute, previous, value });
this.emit('change', {attribute, previous, value});

// If on-the-fly validation is enabled and the value is not blank,
// validate the attribute. It's important to skip blank strings
Expand Down Expand Up @@ -701,7 +707,7 @@ class Model extends Base {
Vue.set(this, 'fatal', false);
Vue.set(this, 'loading', false);

this.emit('fetch', {error: null });
this.emit('fetch', {error: null});
}

/**
Expand Down Expand Up @@ -872,14 +878,21 @@ class Model extends Base {
* @param {Object} errors
*/
setErrors(errors) {
errors = _.defaultTo(errors, {});

// Only pick the first error if we don't want to use an array.
if (this.getOption('useFirstErrorOnly')) {
errors = _.mapValues(errors, _.head);
}

Vue.set(this, '_errors', errors);
}

/**
* @returns {Object} Validation errors on this model.
*/
getErrors() {
return this._errors = _.defaultTo(this._errors, {});
return this._errors;
}

/**
Expand All @@ -893,24 +906,25 @@ class Model extends Base {
/**
* Called when a save request was successful.
*
* @param {Object} response
* @param {Object|null} response
*/
onSaveSuccess(response) {

// Clear errors because the request was successful.
this.clearErrors();

// Update this model with the data that was returned in the response.
this.update(response.getData());
if (response) {
this.update(response.getData());
}

Vue.set(this, 'saving', false);
Vue.set(this, 'fatal', false);

// Automatically add to all registered collections.
this.addToAllCollections();

//
this.emit('save', {error: null });
this.emit('save', {error: null});
}

/**
Expand Down Expand Up @@ -972,7 +986,7 @@ class Model extends Base {
Vue.set(this, 'deleting', false);
Vue.set(this, 'fatal', false);

this.emit('delete', {error: null });
this.emit('delete', {error: null});
}

/**
Expand Down Expand Up @@ -1033,9 +1047,9 @@ class Model extends Base {
return false;
}

//
// Don't save if no data has changed, but consider it a success.
if ( ! this.getOption('saveUnchanged') && ! this.changed()) {
return false;
return true;
}

// Mutate attribute before we save if required to do so.
Expand Down
20 changes: 16 additions & 4 deletions test/Structures/Model.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,17 @@ describe('Model', () => {
describe('errors', () => {
it('should return errors', () => {
let m = new Model();
m.setErrors({a: 1});
expect(m.errors).to.deep.equal({a: 1});
m.setErrors({a: ['Invalid!']});
expect(m.errors).to.deep.equal({a: ['Invalid!']});
})

it('should only return the first error if `useFirstErrorOnly` is set', () => {
let m = new Model({}, null, {useFirstErrorOnly: true});
m.setErrors({a: [1, 2, 3], b: [4, 5]});
expect(m.errors).to.deep.equal({
a: 1,
b: 4,
});
})
})

Expand Down Expand Up @@ -2295,14 +2304,17 @@ describe('Model', () => {
})
})

it('should skip if no attributes have changed when option is enabled', (done) => {
it('should be successful if no attributes have changed when option is enabled', (done) => {
let m = new class extends Model {
defaults() { return {id: 1, name: 'Fred'}}
routes() { return {save: '/collection/save/{id}'}}
options() { return {saveUnchanged: false} }
}

expectRequestToBeSkipped(m.save(), done);
m.save().then((response) => {
expect(response).to.be.null;
done();
})
})

it('should pass if no validation rules are configured', () => {
Expand Down

0 comments on commit bf96890

Please sign in to comment.