Merge branch 'release/1.5.0' into production

This commit is contained in:
Miroslav Bajtoš 2014-05-15 09:36:20 +02:00
commit 800202516c
4 changed files with 192 additions and 52 deletions

View File

@ -138,7 +138,14 @@ function GeoPoint(data) {
assert(data.lat <= 90, 'lat must be <= 90');
assert(data.lat >= -90, 'lat must be >= -90');
/**
* @property {Number} lat The latitude point in degrees. Range: -90 to 90.
*/
this.lat = data.lat;
/**
* @property {Number} lng The longitude point in degrees. Range: -90 to 90.
*/
this.lng = data.lng;
}

View File

@ -11,10 +11,10 @@ exports.Validatable = Validatable;
* This class provides methods that add validation cababilities to models.
* Each of this validations run when `obj.isValid()` method called.
*
* Each configurator can accept n params (n-1 field names and one config). Config
* is {Object} depends on specific validation, but all of them has one common part:
* `message` member. It can be just string, when only one situation possible,
* e.g. `Post.validatesPresenceOf('title', { message: 'can not be blank' });`
* Each configurator can accept *n* params (*n*-1 field names and one config). Config
* is {Object} depends on specific validation, but all of them have a
* `message` member property. It can be just string, when only one situation possible,
* For example: `Post.validatesPresenceOf('title', { message: 'can not be blank' });`
*
* In more complicated cases it can be {Hash} of messages (for each case):
* `User.validatesLengthOf('password', { min: 6, max: 20, message: {min: 'too short', max: 'too long'}});`
@ -24,24 +24,31 @@ function Validatable() {
}
/**
* Validate presence. This validation fails when validated field is blank.
*
* Default error message "can't be blank"
* Validate presence of one or more specified properties.
* Requires a model to include a property to be considered valid; fails when validated field is blank.
*
* For example, validate presence of title
* ```
* Post.validatesPresenceOf('title');
* ```
*Example with custom message
* Validate that model has first, last, and age properties:
* ```
* User.validatesPresenceOf('first', 'last', 'age');
* ```
* Example with custom message
* ```
* Post.validatesPresenceOf('title', {message: 'Cannot be blank'});
* ```
*
* @param {String} propertyName One or more property names.
* @options {Object} errMsg Optional custom error message. Default is "can't be blank"
* @property {String} message Error message to use instead of default.
*/
Validatable.validatesPresenceOf = getConfigurator('presence');
/**
* Validate length. Three kinds of validations: min, max, is.
* Validate length. Require a property length to be within a specified range.
* Three kinds of validations: min, max, is.
*
* Default error messages:
*
@ -61,11 +68,17 @@ Validatable.validatesPresenceOf = getConfigurator('presence');
* User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}});
* User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}});
* ```
* @param {String} propertyName Property name to validate.
* @options {Object} Options
* @property {Number} is Value that property must equal to validate.
* @property {Number} min Value that property must be less than to be valid.
* @property {Number} max Value that property must be less than to be valid.
* @property {Object} message Optional Object with string properties for custom error message for each validation: is, min, or max
*/
Validatable.validatesLengthOf = getConfigurator('length');
/**
* Validate numericality.
* Validate numericality. Requires a value for property to be either an integer or number.
*
* Example
* ```
@ -73,19 +86,17 @@ Validatable.validatesLengthOf = getConfigurator('length');
* User.validatesNumericalityOf('age', {int: true, message: { int: '...' }});
* ```
*
* Default error messages:
*
* @param {String} propertyName Property name to validate.
* @options {Object} Options
* @property {Boolean} int If true, then property must be an integer to be valid.
* @property {Object} message Optional object with string properties for 'int' for integer validation. Default error messages:
* - number: is not a number
* - int: is not an integer
*
* @sync
* @nocode
* @see helper/validateNumericality
*/
Validatable.validatesNumericalityOf = getConfigurator('numericality');
/**
* Validate inclusion in set
* Validate inclusion in set. Require a value for property to be in the specified array.
*
* Example:
* ```
@ -95,33 +106,35 @@ Validatable.validatesNumericalityOf = getConfigurator('numericality');
* });
* ```
*
* Default error message: is not included in the list
*
* @sync
* @nocode
* @see helper/validateInclusion
* @param {String} propertyName Property name to validate.
* @options {Object} Options
* @property {Array} in Array Property must match one of the values in the array to be valid.
* @property {String} message Optional error message if property is not valid. Default error message: "is not included in the list".
*/
Validatable.validatesInclusionOf = getConfigurator('inclusion');
/**
* Validate exclusion
* Validate exclusion. Require a property value not be in the specified array.
*
* Example: `Company.validatesExclusionOf('domain', {in: ['www', 'admin']});`
*
* Default error message: is reserved
*
* @nocode
* @see helper/validateExclusion
* @param {String} propertyName Property name to validate.
* @options {Object} Options
* @property {Array} in Array Property must match one of the values in the array to be valid.
* @property {String} message Optional error message if property is not valid. Default error message: "is reserved".
*/
Validatable.validatesExclusionOf = getConfigurator('exclusion');
/**
* Validate format
* Validate format. Require a model to include a property that matches the given format.
*
* Default error message: is invalid
* Require a model to include a property that matches the given format. Example:
* `User.validatesFormat('name', {with: /\w+/});`
*
* @nocode
* @see helper/validateFormat
* @param {String} propertyName Property name to validate.
* @options {Object} Options
* @property {RegExp} with Regular expression to validate format.
* @property {String} message Optional error message if property is not valid. Default error message: " is invalid".
*/
Validatable.validatesFormatOf = getConfigurator('format');
@ -178,19 +191,32 @@ Validatable.validate = getConfigurator('custom');
Validatable.validateAsync = getConfigurator('custom', {async: true});
/**
* Validate uniqueness
* Validate uniqueness. Ensure the value for property is unique in the collection of models.
* Not available for all connectors. Currently supported with these connectors:
* - In Memory
* - Oracle
* - MongoDB
*
* Default error message: is not unique
* ```
* // The login must be unique across all User instances.
* User.validatesUniquenessOf('login');
*
* @async
* @nocode
* @see helper/validateUniqueness
* // Assuming SiteUser.belongsTo(Site)
* // The login must be unique within each Site.
* SiteUser.validateUniquenessOf('login', { scopedTo: ['siteId'] });
* ```
* @param {String} propertyName Property name to validate.
* @options {Object} Options
* @property {RegExp} with Regular expression to validate format.
* @property {Array.<String>} scopedTo List of properties defining the scope.
* @property {String} message Optional error message if property is not valid. Default error message: "is not unique".
*/
Validatable.validatesUniquenessOf = getConfigurator('uniqueness', {async: true});
// implementation of validators
/**
/*!
* Presence validator
*/
function validatePresence(attr, conf, err) {
@ -199,7 +225,7 @@ function validatePresence(attr, conf, err) {
}
}
/**
/*!
* Length validator
*/
function validateLength(attr, conf, err) {
@ -217,7 +243,7 @@ function validateLength(attr, conf, err) {
}
}
/**
/*!
* Numericality validator
*/
function validateNumericality(attr, conf, err) {
@ -231,7 +257,7 @@ function validateNumericality(attr, conf, err) {
}
}
/**
/*!
* Inclusion validator
*/
function validateInclusion(attr, conf, err) {
@ -242,7 +268,7 @@ function validateInclusion(attr, conf, err) {
}
}
/**
/*!
* Exclusion validator
*/
function validateExclusion(attr, conf, err) {
@ -253,7 +279,7 @@ function validateExclusion(attr, conf, err) {
}
}
/**
/*!
* Format validator
*/
function validateFormat(attr, conf, err) {
@ -268,19 +294,28 @@ function validateFormat(attr, conf, err) {
}
}
/**
/*!
* Custom validator
*/
function validateCustom(attr, conf, err, done) {
conf.customValidator.call(this, err, done);
}
/**
/*!
* Uniqueness validator
*/
function validateUniqueness(attr, conf, err, done) {
var cond = {where: {}};
cond.where[attr] = this[attr];
if (conf && conf.scopedTo) {
conf.scopedTo.forEach(function(k) {
var val = this[k];
if (val !== undefined)
cond.where[k] = this[k];
}, this);
}
this.constructor.find(cond, function (error, found) {
if (error) {
return err();
@ -312,16 +347,14 @@ function getConfigurator(name, opts) {
}
/**
* This method performs validation, triggers validation hooks.
* Before validation `obj.errors` collection cleaned.
* This method performs validation and triggers validation hooks.
* Before validation the `obj.errors` collection is cleaned.
* Each validation can add errors to `obj.errors` collection.
* If collection is not blank, validation failed.
*
* @warning This method can be called as sync only when no async validation
* NOTE: This method can be called as synchronous only when no asynchronous validation is
* configured. It's strongly recommended to run all validations as asyncronous.
*
* Returns true if no async validation configured and all passed
*
* Example: ExpressJS controller: render user if valid, show flash otherwise
* ```
* user.isValid(function (valid) {
@ -329,7 +362,21 @@ function getConfigurator(name, opts) {
* else res.flash('error', 'User is not valid'), console.log(user.errors), res.redirect('/users');
* });
* ```
* Another example:
* ```
* user.isValid(function (valid) {
* if (!valid) {
* console.log(user.errors);
* // => hash of errors
* // => {
* // => username: [errmessage, errmessage, ...],
* // => email: ...
* // => }
* }
* });
* ```
* @param {Function} callback called with (valid)
* @returns {Boolean} True if no asynchronouse validation is configured and all properties pass validation.
*/
Validatable.prototype.isValid = function (callback, data) {
var valid = true, inst = this, wait = 0, async = false;
@ -521,7 +568,7 @@ function nullCheck(attr, conf, err) {
return false;
}
/**
/*!
* Return true when v is undefined, blank array, null or empty string
* otherwise returns false
*
@ -586,6 +633,56 @@ function ErrorCodes(messages) {
});
}
/**
* ValidationError is raised when the application attempts to save an invalid model instance.
* Example:
* ```
* {
* "name": "ValidationError",
* "status": 422,
* "message": "The Model instance is not valid. \
* See `details` property of the error object for more info.",
* "statusCode": 422,
* "details": {
* "context": "user",
* "codes": {
* "password": [
* "presence"
* ],
* "email": [
* "uniqueness"
* ]
* },
* "messages": {
* "password": [
* "can't be blank"
* ],
* "email": [
* "Email already exists"
* ]
* }
* },
* }
* ```
* You might run into situations where you need to raise a validation error yourself, for example in a "before" hook or a
* custom model method.
* ```
* MyModel.prototype.preflight = function(changes, callback) {
* // Update properties, do not save to db
* for (var key in changes) {
* model[key] = changes[key];
* }
*
* if (model.isValid()) {
* return callback(null, { success: true });
* }
*
* // This line shows how to create a ValidationError
* err = new ValidationError(model);
* callback(err);
* }
* ```
*/
function ValidationError(obj) {
if (!(this instanceof ValidationError)) return new ValidationError(obj);

View File

@ -1,6 +1,6 @@
{
"name": "loopback-datasource-juggler",
"version": "1.4.0",
"version": "1.5.0",
"description": "LoopBack DataSoure Juggler",
"keywords": [
"StrongLoop",

View File

@ -1,5 +1,6 @@
// This test written in mocha+should.js
var should = require('./init.js');
var async = require('async');
var j = require('../'), db, User;
var ValidationError = j.ValidationError;
@ -172,6 +173,41 @@ describe('validations', function () {
})).should.not.be.ok;
});
it('should support multi-key constraint', function(done) {
var EMAIL = 'user@xample.com';
var SiteUser = db.define('SiteUser', {
siteId: String,
email: String
});
SiteUser.validatesUniquenessOf('email', { scopedTo: ['siteId'] });
async.waterfall([
function automigrate(next) {
db.automigrate(next);
},
function createSite1User(next) {
SiteUser.create(
{ siteId: 1, email: EMAIL },
next);
},
function createSite2User(user1, next) {
SiteUser.create(
{ siteId: 2, email: EMAIL },
next);
},
function validateDuplicateUser(user2, next) {
var user3 = new SiteUser({ siteId: 1, email: EMAIL });
user3.isValid(function(valid) {
valid.should.be.false;
next();
});
}
], function(err) {
if (err && err.name == 'ValidationError') {
console.error('ValidationError:', err.details.messages);
}
done(err);
});
});
});
describe('format', function () {