Skip to content

Commit

Permalink
Merge pull request #96 from offirgolan/public-options-api
Browse files Browse the repository at this point in the history
validation result options hash
  • Loading branch information
offirgolan committed Jan 7, 2016
2 parents 7433fdb + 75c68f7 commit 1057203
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 11 deletions.
10 changes: 6 additions & 4 deletions addon/validations/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ function createCPValidationFor(attribute, validations) {
value = validator.validate(attrValue, options, model, attribute);
}

return validationReturnValueHandler(attribute, value, model);
return validationReturnValueHandler(attribute, value, model, validator);
});

return ValidationResultCollection.create({
Expand Down Expand Up @@ -314,17 +314,19 @@ function getCPDependentKeysFor(attribute, validations) {
* @param {Object} model
* @return {ValidationResult}
*/
function validationReturnValueHandler(attribute, value, model) {
function validationReturnValueHandler(attribute, value, model, validator) {
var result, _promise;

if (canInvoke(value, 'then')) {
_promise = Promise.resolve(value);
result = ValidationResult.create({
attribute, _promise, model
attribute, _promise, model,
_validator: validator
});
} else {
result = ValidationResult.create({
attribute, model
attribute, model,
_validator: validator
});
result.update(value);
}
Expand Down
79 changes: 73 additions & 6 deletions addon/validations/result-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const {
RSVP,
computed,
isEmpty,
isArray,
isNone,
A: emberArray
} = Ember;

Expand Down Expand Up @@ -224,27 +226,92 @@ export default Ember.Object.extend({
return get(this, 'errors.0');
})),

/**
* All built options of the validators associated with the results in this collection grouped by validator type
*
* ```javascript
* // Given the following validators
* {
* username: [
* validator('presence', true),
* validator('length', { max: 15 }),
* validator('format', { regex: /foo/ }),
* validator('format', { regex: /bar/ }),
* ]
* }
* ```
*
* ```js
* get(user, 'validations.attrs.username.options')
* ```
*
* The above will return the following
* ```js
* {
* 'presence': { presence: true},
* 'length': { max: 15 },
* 'regex': [{ regex: /foo/ }, { regex: /bar/ }]
* }
* ```
*
* @property options
* @readOnly
* @type {Ember.ComputedProperty | Object}
*/
options: computed('content.[]', function() {
return this._groupValidatorOptions();
}),

/**
* @property _promise
* @async
* @private
* @type {Ember.ComputedProperty | Promise}
*/
_promise: computed('content.@each._promise', cycleBreaker(function() {
var promises = get(this, 'content').getEach('_promise');
let promises = get(this, 'content').getEach('_promise');
if (!isEmpty(promises)) {
return RSVP.all(compact(flatten(promises)));
}
})),

/**
* @property value
* @private
* @type {Ember.ComputedProperty}
* @private
*/
value: computed('isAsync', cycleBreaker(function() {
var isAsync = get(this, 'isAsync');
var promise = get(this, '_promise');
return isAsync ? promise : this;
}))
return get(this, 'isAsync') ? get(this, '_promise') : this;
})),

/**
* Used by the `options` property to create a hash from the `content` that is grouped by validator type.
* If there is more than 1 of a type, it groups it into an array of option objects.
*
* @method _groupValidatorOptions
* @return {Object}
* @private
*/
_groupValidatorOptions() {
let validators = get(this, 'content').getEach('_validator');
return validators.reduce((options, v) => {
if(isNone(v) || isNone(get(v, '_type'))) {
return options;
}

let type = get(v, '_type');
let vOpts = get(v, 'options');

if(options[type]) {
if(isArray(options[type])) {
options[type].push(vOpts);
} else {
options[type] = [options[type], vOpts];
}
} else {
options[type] = vOpts;
}
return options;
}, {});
}
});
8 changes: 8 additions & 0 deletions addon/validations/result.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ export default Ember.Object.extend({
*/
_promise: undefined,

/**
* The validator that returned this result
* @property _validator
* @private
* @type {Validator}
*/
_validator: null,

/**
* @property isValid
* @readOnly
Expand Down
17 changes: 16 additions & 1 deletion addon/validations/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,25 @@ const {
* The return value must be a `string`. If nothing is returned (`undefined`), defaults to the default error message of the specified type.
*
* Within this function, the context is set to that of the current validator. This gives you access to the model, defaultMessages, options and more.
*
*
* ## Function Based Validators
*
* A validator can also be declared with a function. The function will be then wrapped in the [Base Validator](./base.md) class and used just like any other pre-defined validator.
*
* ```javascript
* // Example
* validator(function(value, options, model, attribute) {
* return value === options.username ? true : `must be ${options.username}`;
* } , {
* username: 'John' // Any options can be passed here
* })
* ```
*
* @module Validators
* @main Validators
*/

export default function(arg1, options) {
options = isNone(options) ? {} : options;

Expand Down
35 changes: 35 additions & 0 deletions tests/integration/validations/factory-general-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Ember from 'ember';
import setupObject from '../../helpers/setup-object';
import DefaultMessages from 'dummy/validators/messages';
import PresenceValidator from 'dummy/validators/presence';
import LengthValidator from 'dummy/validators/length';
import { validator, buildValidations } from 'ember-cp-validations';
import { moduleFor, test } from 'ember-qunit';

Expand Down Expand Up @@ -387,3 +389,36 @@ test("destroy object clears caches - multiple object instances", function(assert
assert.equal(Object.keys(objects[0].get('validations._debouncedValidations')).length, 0);
});
});

test("validation result options hash", function(assert) {
this.register('validator:length', LengthValidator);
this.register('validator:presence', PresenceValidator);

var Validations = buildValidations({
firstName: {
description: 'First Name',
validators: [
validator(Validators.presence, {}),
validator(Validators.presence, { presence: true} ),
validator('presence', true),
validator('length', {min: 1, max: 5})
]
}
});
var object = setupObject(this, Ember.Object.extend(Validations));
var options = object.get('validations.attrs.firstName.options');

assert.ok(options);
assert.deepEqual(Object.keys(options).sort(), ['presence', 'length', 'function'].sort());
assert.ok(Ember.isArray(options['function']) && options['function'].length === 2);
assert.ok(options.presence.presence);
assert.equal(options.length.min, 1);
assert.ok(options['function'][1].presence);

// Default options built into option objects
assert.equal(options.length.description, 'First Name');
assert.equal(options.presence.description, 'First Name');
assert.equal(options['function'][0].description, 'First Name');

});

0 comments on commit 1057203

Please sign in to comment.