Add Validations Layer and Post Validations

closes #2893, issue #2850, issue #2856
- this is a stable, but quick and dirty validations layer for the time constraints
- this could be replaced with a unified server/client layer later. the infrastructure is there.
- create a validation engine mixin to match validators with models
- override the save method in the mixin to perform validations first
- create a post validator
- fixup calls to .save() to make sure they catch errors properly
This commit is contained in:
David Arvelo 2014-06-20 17:36:44 -04:00
parent 28d10a9e2f
commit 6020702462
7 changed files with 126 additions and 25 deletions

View File

@ -23,7 +23,9 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
self.notifications.showSuccess('Successfully converted to ' + (val ? 'static page' : 'post'));
return self.get('page');
}, this.notifications.showErrors);
}, function (errors) {
self.notifications.showErrors(errors);
});
}
return this.get('page');
@ -103,7 +105,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
// Because the server transforms the candidate slug by stripping
// certain characters and appending a number onto the end of slugs
// to enforce uniqueness, there are cases where we can get back a
// to enforce uniqueness, there are cases where we can get back a
// candidate slug that is a duplicate of the original except for
// the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)
@ -135,7 +137,9 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
return self.get('model').save().then(function () {
self.notifications.showSuccess('Permalink successfully changed to <strong>' +
self.get('slug') + '</strong>.');
}, self.notifications.showErrors);
}, function (errors) {
self.notifications.showErrors(errors);
});
});
},
@ -183,12 +187,12 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
//Validation complete
this.set('published_at', newPublishedAt);
//@ TODO: Make sure we're saving ONLY the publish date here,
// Don't want to accidentally save text the user's been working on.
this.get('model').save('published_at').then(function () {
this.get('model').save().then(function () {
self.notifications.showSuccess('Publish date successfully changed to <strong>' +
formatDate(self.get('published_at')) + '</strong>.');
}, this.notifications.showErrors);
}, function (errors) {
self.notifications.showErrors(errors);
});
}
}
});

View File

@ -9,8 +9,8 @@ var PostController = Ember.ObjectController.extend({
this.get('model').save().then(function () {
self.notifications.showSuccess('Post successfully marked as ' + (featured ? 'featured' : 'not featured') + '.');
}).catch(function () {
self.notifications.showError('An error occured while saving the post.');
}).catch(function (errors) {
self.notifications.showErrors(errors);
});
}
}

View File

@ -109,22 +109,26 @@ var EditorControllerMixin = Ember.Mixin.create(MarkerManager, {
actions: {
save: function () {
var status = this.get('willPublish') ? 'published' : 'draft',
model = this.get('model'),
self = this;
// set markdown equal to what's in the editor, minus the image markers.
this.set('markdown', this.getMarkdown().withoutMarkers);
this.set('status', status);
return this.get('model').save().then(function (model) {
return model.save().then(function () {
model.updateTags();
// `updateTags` triggers `isDirty => true`.
// for a saved model it would otherwise be false.
self.set('isDirty', false);
self.notifications.showSuccess('Post status saved as <strong>' +
model.get('status') + '</strong>.');
model.get('status') + '</strong>.');
return model;
}, this.notifications.showErrors);
}, function (errors) {
self.notifications.showErrors(errors);
});
},
setSaveType: function (newType) {

View File

@ -0,0 +1,69 @@
import { getRequestErrorMessage } from 'ghost/utils/ajax';
import ValidatorExtensions from 'ghost/utils/validator-extensions';
import PostValidator from 'ghost/validators/post';
ValidatorExtensions.init();
var ValidationEngine = Ember.Mixin.create({
validators: {
post: PostValidator
},
validate: function () {
var self = this,
type = this.get('validationType'),
validator = this.get('validators.' + type);
return new Ember.RSVP.Promise(function (resolve, reject) {
if (!type || !validator) {
return reject('The validator specified, "' + type + '", did not exist!');
}
var validationErrors = validator.validate(self);
if (Ember.isEmpty(validationErrors)) {
return resolve();
}
return reject(validationErrors);
});
},
// override save to do validation first
save: function () {
var self = this,
// this is a hack, but needed for async _super calls.
// ref: https://github.com/emberjs/ember.js/pull/4301
_super = this.__nextSuper;
// If validation fails, reject with validation errors.
// If save to the server fails, reject with server response.
return this.validate().then(function () {
return _super.call(self);
}).catch(function (result) {
var message = 'There was an error saving this ' + self.get('validationType');
if (Ember.isArray(result)) {
// get validation error messages
message += ': ' + result.mapBy('message').join(' ');
} else if (typeof result === 'object') {
// Get messages from server response
message += ': ' + getRequestErrorMessage(result);
} else if (typeof result === 'string') {
message += ': ' + result;
} else {
message += '.';
}
// set format for notifications.showErrors
message = [{ message: message }];
return new Ember.RSVP.Promise(function (resolve, reject) {
reject(message);
});
});
}
});
export default ValidationEngine;

View File

@ -1,4 +1,8 @@
var Post = DS.Model.extend({
import ValidationEngine from 'ghost/mixins/validation-engine';
var Post = DS.Model.extend(ValidationEngine, {
validationType: 'post',
uuid: DS.attr('string'),
title: DS.attr('string'),
slug: DS.attr('string'),
@ -24,18 +28,6 @@ var Post = DS.Model.extend({
isPublished: Ember.computed.equal('status', 'published'),
isDraft: Ember.computed.equal('status', 'draft'),
validate: function () {
var validationErrors = [];
if (!this.get('title.length')) {
validationErrors.push({
message: 'You must specify a title for the post.'
});
}
return validationErrors;
}.property('title'),
// remove client-generated tags, which have `id: null`.
// Ember Data won't recognize/update them automatically
// when returned from the server with ids.

View File

@ -0,0 +1,15 @@
function init() {
// Provide a few custom validators
//
validator.extend('empty', function (str) {
return Ember.isBlank(str);
});
validator.extend('notContains', function (str, badString) {
return !_.contains(str, badString);
});
}
export default {
init: init
};

View File

@ -0,0 +1,17 @@
var PostValidator = Ember.Object.create({
validate: function (model) {
var validationErrors = [],
title = model.get('title');
if (validator.empty(title)) {
validationErrors.push({
message: 'You must specify a title for the post.'
});
}
return validationErrors;
}
});
export default PostValidator;