Ghost/ghost/admin/app/mixins/validation-engine.js
Kevin Ansfield cb59388c5b 💄🐷 sort-imports eslint rule (#712)
no issue

- adds `eslint-plugin-sort-imports-es6-autofix` dependency
  - implements ESLint's base `sort-imports` rule but has a distinction in that `import {foo} from 'bar';` is considered `multiple` rather than `single`
  - fixes ESLint's autofix behaviour so `eslint --fix` will actually fix the sort order
- updates all unordered import rules by using `eslint --fix`

With the increased number of `import` statements since Ember+ecosystem started moving towards es6 modules I've found it frustrating at times trying to search through randomly ordered import statements. Recently I've been sorting imports manually when I've added new code or touched old code so I thought I'd add an ESLint rule to codify it.
2017-05-29 20:50:03 +02:00

162 lines
5.9 KiB
JavaScript

import DS from 'ember-data';
import InviteUserValidator from 'ghost-admin/validators/invite-user';
import Mixin from 'ember-metal/mixin';
import Model from 'ember-data/model';
import NavItemValidator from 'ghost-admin/validators/nav-item';
import PostValidator from 'ghost-admin/validators/post';
import RSVP from 'rsvp';
import ResetValidator from 'ghost-admin/validators/reset';
import SettingValidator from 'ghost-admin/validators/setting';
import SetupValidator from 'ghost-admin/validators/setup';
import SigninValidator from 'ghost-admin/validators/signin';
import SignupValidator from 'ghost-admin/validators/signup';
import SlackIntegrationValidator from 'ghost-admin/validators/slack-integration';
import SubscriberValidator from 'ghost-admin/validators/subscriber';
import TagSettingsValidator from 'ghost-admin/validators/tag-settings';
import UserValidator from 'ghost-admin/validators/user';
import ValidatorExtensions from 'ghost-admin/utils/validator-extensions';
import {A as emberA, isEmberArray} from 'ember-array/utils';
const {Errors} = DS;
// our extensions to the validator library
ValidatorExtensions.init();
/**
* The class that gets this mixin will receive these properties and functions.
* It will be able to validate any properties on itself (or the model it passes to validate())
* with the use of a declared validator.
*/
export default Mixin.create({
// these validators can be passed a model to validate when the class that
// mixes in the ValidationEngine declares a validationType equal to a key on this object.
// the model is either passed in via `this.validate({ model: object })`
// or by calling `this.validate()` without the model property.
// in that case the model will be the class that the ValidationEngine
// was mixed into, i.e. the controller or Ember Data model.
validators: {
inviteUser: InviteUserValidator,
navItem: NavItemValidator,
post: PostValidator,
reset: ResetValidator,
setting: SettingValidator,
setup: SetupValidator,
signin: SigninValidator,
signup: SignupValidator,
slackIntegration: SlackIntegrationValidator,
subscriber: SubscriberValidator,
tag: TagSettingsValidator,
user: UserValidator
},
// This adds the Errors object to the validation engine, and shouldn't affect
// ember-data models because they essentially use the same thing
errors: null,
// Store whether a property has been validated yet, so that we know whether or not
// to show error / success validation for a field
hasValidated: null,
init() {
this._super(...arguments);
this.set('errors', Errors.create());
this.set('hasValidated', emberA());
},
/**
* Passes the model to the validator specified by validationType.
* Returns a promise that will resolve if validation succeeds, and reject if not.
* Some options can be specified:
*
* `model: Object` - you can specify the model to be validated, rather than pass the default value of `this`,
* the class that mixes in this mixin.
*
* `property: String` - you can specify a specific property to validate. If
* no property is specified, the entire model will be
* validated
*/
validate(opts) {
let model = this;
let hasValidated,
type,
validator;
opts = opts || {};
if (opts.model) {
model = opts.model;
} else if (this instanceof Model) {
model = this;
} else if (this.get('model')) {
model = this.get('model');
}
type = this.get('validationType') || model.get('validationType');
validator = this.get(`validators.${type}`) || model.get(`validators.${type}`);
hasValidated = this.get('hasValidated');
opts.validationType = type;
return new RSVP.Promise((resolve, reject) => {
let passed;
if (!type || !validator) {
return reject([`The validator specified, "${type}", did not exist!`]);
}
if (opts.property) {
// If property isn't in `hasValidated`, add it to mark that this field can show a validation result
hasValidated.addObject(opts.property);
model.get('errors').remove(opts.property);
} else {
model.get('errors').clear();
}
passed = validator.check(model, opts.property);
return (passed) ? resolve() : reject();
});
},
/**
* The primary goal of this method is to override the `save` method on Ember Data models.
* This allows us to run validation before actually trying to save the model to the server.
* You can supply options to be passed into the `validate` method, since the ED `save` method takes no options.
*/
save(options) {
let {_super} = this;
options = options || {};
options.wasSave = true;
// model.destroyRecord() calls model.save() behind the scenes.
// in that case, we don't need validation checks or error propagation,
// because the model itself is being destroyed.
if (this.get('isDeleted')) {
return this._super(...arguments);
}
// If validation fails, reject with validation errors.
// If save to the server fails, reject with server response.
return this.validate(options).then(() => {
if (typeof this.beforeSave === 'function') {
this.beforeSave();
}
return _super.call(this, options);
}).catch((result) => {
// server save failed or validator type doesn't exist
if (result && !isEmberArray(result)) {
throw result;
}
return RSVP.reject(result);
});
},
actions: {
validate(property) {
this.validate({property});
}
}
});