diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..e69de29
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d4a62c7
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+ - "0.10"
+before_script:
+ - npm install -g gulp
\ No newline at end of file
diff --git a/Gulpfile.js b/Gulpfile.js
new file mode 100644
index 0000000..72f3610
--- /dev/null
+++ b/Gulpfile.js
@@ -0,0 +1,63 @@
+'use strict';
+
+var gulp = require('gulp');
+var sourcemaps = require('gulp-sourcemaps');
+var plugins = require('gulp-load-plugins')();
+var gutil = require('gulp-util');
+var qunit = require('gulp-qunit');
+var shell = require('gulp-shell');
+var merge = require('merge-stream');
+var size = require('gulp-check-filesize');
+
+var build = {
+ filename: 'rekord-validation.js',
+ minified: 'rekord-validation.min.js',
+ output: './build/',
+ include: [
+ './src/header.js',
+ './src/lib/**/*.js',
+ './src/footer.js'
+ ]
+};
+
+var executeMinifiedBuild = function(props)
+{
+ return function() {
+ return gulp
+ .src( props.include )
+ .pipe( sourcemaps.init() )
+ .pipe( plugins.concat( props.minified ) )
+ .pipe( plugins.uglify().on('error', gutil.log) )
+ .pipe( sourcemaps.write('.') )
+ .pipe( size({enableGzip: true}) )
+ .pipe( gulp.dest( props.output ) )
+ ;
+ };
+};
+
+var executeBuild = function(props)
+{
+ return function() {
+ return gulp
+ .src( props.include )
+ .pipe( plugins.concat( props.filename ) )
+ .pipe( size({enableGzip: true}) )
+ .pipe( gulp.dest( props.output ) )
+ ;
+ };
+};
+
+var executeTest = function(file)
+{
+ return function() {
+ return gulp.src( file ).pipe( qunit() );
+ };
+};
+
+gulp.task( 'docs', shell.task(['./node_modules/.bin/jsdoc -c jsdoc.json']));
+gulp.task( 'clean', shell.task(['rm -rf build/*.js', 'rm -rf build/*.map']));
+gulp.task( 'test', executeTest( './test/index.html' ) );
+
+gulp.task( 'js:min', executeMinifiedBuild( build ) );
+gulp.task( 'js', executeBuild( build ) );
+gulp.task( 'default', ['js:min', 'js']);
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e44b13a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2015-2016 Magnos, LLC. https://github.com/Rekord/rekord-validation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f86bf1f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+#
Rekord Validation
+
+[](https://travis-ci.org/Rekord/rekord-validation)
+[](https://david-dm.org/Rekord/rekord-validation#info=devDependencies)
+[](https://david-dm.org/Rekord/rekord-validation)
+[](https://github.com/Rekord/rekord-validation/blob/master/LICENSE)
+[]()
+
+Rekord is a javascript REST ORM that is offline and real-time capable.
+
+rekord-validation adds rules, expressions, transforms, and custom validation functionality.
+
+**Installation**
+
+The easiest way to install rekord-validation is through bower via `bower install rekord-validation`.
+
+- rekord-validation.js is `46KB` (`7.55KB` gzipped)
+- rekord-validation.min.js is `18KB` (`5.24KB` gzipped)
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..91aa9a9
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,25 @@
+{
+ "name": "rekord-validation",
+ "version": "1.0.0",
+ "homepage": "https://github.com/Rekord/rekord-validation",
+ "authors": [
+ "Philip Diffenderfer "
+ ],
+ "description": "Advanced validation rules for rekord",
+ "main": "build/rekord-validation.js",
+ "keywords": [
+ "javascript",
+ "orm",
+ "offline",
+ "realtime",
+ "validation"
+ ],
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests"
+ ]
+}
diff --git a/build/rekord-validation.js b/build/rekord-validation.js
new file mode 100644
index 0000000..f1cefe9
--- /dev/null
+++ b/build/rekord-validation.js
@@ -0,0 +1,2013 @@
+(function(global, Rekord, undefined)
+{
+ var Model = Rekord.Model;
+ var Database = Rekord.Database;
+ var Promise = Rekord.Promise;
+ var Collection = Rekord.Collection;
+
+ var isEmpty = Rekord.isEmpty;
+ var isString = Rekord.isString;
+ var isArray = Rekord.isArray;
+ var isObject = Rekord.isObject;
+ var isFunction = Rekord.isFunction;
+ var isDate = Rekord.isDate;
+ var isNumber = Rekord.isNumber;
+ var isBoolean = Rekord.isBoolean;
+ var isValue = Rekord.isValue;
+ var isPrimitiveArray = Rekord.isPrimitiveArray;
+ var isRegExp = Rekord.isRegExp;
+
+ var noop = Rekord.noop;
+ var equalsCompare = Rekord.equalsCompare;
+ var equals = Rekord.equals;
+ var indexOf = Rekord.indexOf;
+ var sizeof = Rekord.sizeof;
+
+ var split = Rekord.split;
+ var transfer = Rekord.transfer;
+ var format = Rekord.format;
+
+ var parseDate = Rekord.parseDate;
+
+ var addMethod = Rekord.addMethod;
+ var replaceMethod = Rekord.replaceMethod;
+
+function tryParseFloat(x)
+{
+ var parsed = parseFloat( x );
+
+ if ( !isNaN( parsed ) )
+ {
+ x = parsed;
+ }
+
+ return x;
+}
+
+function tryParseInt(x)
+{
+ var parsed = parseInt( x );
+
+ if ( !isNaN( parsed ) )
+ {
+ x = parsed;
+ }
+
+ return x;
+}
+
+function startOfDay(d)
+{
+ if ( isDate( d ) )
+ {
+ d.setHours( 0, 0, 0, 0 );
+ }
+ else if ( isNumber( d ) )
+ {
+ d = d - (d % 86400000);
+ }
+
+ return d;
+}
+
+function endOfDay(d)
+{
+ if ( isDate( d ) )
+ {
+ d.setHours( 23, 59, 59, 999 );
+ }
+ else if ( isNumber( d ) )
+ {
+ d = d - (d % 86400000) + 86400000 - 1;
+ }
+
+ return d;
+}
+
+function ruleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ checkNoParams( ruleName, field, params );
+
+ var messageTemplate = determineMessage( ruleName, message );
+
+ return function(value, model, setMessage)
+ {
+ function setValue( newValue )
+ {
+ value = newValue;
+ }
+
+ if ( isInvalid( value, model, setValue ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+function determineMessage(ruleName, message)
+{
+ return message || Validation.Rules[ ruleName ].message;
+}
+
+function joinFriendly(arr, lastSeparator, itemSeparator, getAlias)
+{
+ var copy = arr.slice();
+
+ if ( getAlias )
+ {
+ for (var i = 0; i < copy.length; i++)
+ {
+ copy[ i ] = getAlias( copy[ i ] );
+ }
+ }
+
+ var last = copy.pop();
+ var lastSeparator = lastSeparator || 'and';
+ var itemSeparator = itemSeparator || ', ';
+
+ switch (copy.length) {
+ case 0:
+ return last;
+ case 1:
+ return copy[ 0 ] + ' ' + lastSeparator + ' ' + last;
+ default:
+ return copy.join( itemSeparator ) + itemSeparator + lastSeparator + ' ' + last;
+ }
+}
+
+function mapFromArray(arr, value)
+{
+ var map = {};
+
+ for (var i = 0; i < arr.length; i++)
+ {
+ map[ arr[ i ] ] = value;
+ }
+
+ return map;
+}
+
+function checkNoParams(ruleName, field, params)
+{
+ if ( params )
+ {
+ throw 'the rule ' + ruleName + ' for field ' + field + ' has no arguments';
+ }
+}
+
+function generateMessage(field, alias, value, model, message, extra)
+{
+ if ( isFunction( message ) )
+ {
+ message = message( field, alias, value, model, extra );
+ }
+
+ var base = {};
+ base.$field = field;
+ base.$alias = alias;
+ base.$value = value;
+
+ transfer( model, base );
+
+ if ( isObject( extra ) )
+ {
+ transfer( extra, base );
+ }
+
+ return format( message, base );
+}
+
+Rekord.on( Rekord.Events.Plugins, function(model, db, options)
+{
+ var validation = options.validation || Database.Defaults.validation;
+
+ if ( isEmpty( validation ) )
+ {
+ return;
+ }
+
+ var rules = validation.rules || {};
+ var messages = validation.messages || {};
+ var aliases = validation.aliases || {};
+ var required = !!validation.required;
+
+ function getAlias(field)
+ {
+ return aliases[ field ] || field;
+ }
+
+ db.validations = {};
+
+ for ( var field in rules )
+ {
+ db.validations[ field ] = Validation.parseRules( rules[ field ], field, db, getAlias, messages[ field ] )
+ }
+
+ addMethod( model.prototype, '$validate', function()
+ {
+ var $this = this;
+
+ this.$trigger( Model.Events.PreValidate, [this] );
+
+ this.$valid = true;
+ this.$validations = {};
+ this.$validationMessages.length = 0;
+
+ for (var field in db.validations)
+ {
+ var chain = db.validations[ field ];
+ var value = this.$get( field );
+ var fieldValid = true;
+
+ var setMessage = function(message)
+ {
+ // Only accept for the first valid message
+ if ( message && fieldValid )
+ {
+ fieldValid = false;
+
+ $this.$validations[ field ] = message;
+ $this.$validationMessages.push( message );
+ $this.$valid = false;
+ }
+ };
+
+ for (var i = 0; i < chain.length && fieldValid && value !== Validation.Stop; i++)
+ {
+ value = chain[ i ]( value, this, setMessage );
+ }
+ }
+
+ this.$trigger( this.$valid ? Model.Events.ValidatePass : Model.Events.ValidateFail, [this] );
+
+ return this.$valid;
+ });
+
+ replaceMethod( model.prototype, '$init', function($init)
+ {
+ return function()
+ {
+ this.$valid = undefined;
+ this.$validations = {};
+ this.$validationMessages = [];
+
+ return $init.apply( this, arguments );
+ };
+ });
+
+ if ( required )
+ {
+ replaceMethod( model.prototype, '$save', function($save)
+ {
+ return function()
+ {
+ if ( this.$isDeleted() )
+ {
+ Rekord.debug( Rekord.Debugs.SAVE_DELETED, this.$db, this );
+
+ return Promise.resolve( this );
+ }
+
+ if ( !this.$validate() )
+ {
+ return Promise.resolve( this );
+ }
+
+ return $save.apply( this, arguments );
+ };
+ });
+ }
+});
+
+Model.Events.PreValidate = 'pre-validate';
+
+Model.Events.ValidatePass = 'validate-pass';
+
+Model.Events.ValidateFail = 'validate-fail';
+
+var Validation =
+{
+ Rules: {},
+ Expression: {},
+ Expressions: [],
+ Delimiter: /([|])/,
+ Escape: '\\',
+ RuleSeparator: ':',
+ Stop: {},
+
+ parseRules: function(rules, field, database, getAlias, message)
+ {
+ var validators = [];
+
+ if ( isString( rules ) )
+ {
+ rules = split( rules, this.Delimiter, this.Escape );
+ }
+
+ if ( isArray( rules ) )
+ {
+ for (var i = 0; i < rules.length; i++)
+ {
+ var rule = rules[ i ];
+ var validator = this.parseRule( rule, field, database, getAlias, message );
+
+ validators.push( validator );
+ }
+ }
+ else if ( isObject( rules ) )
+ {
+ for (var rule in rules)
+ {
+ var ruleMessageOrData = rules[ rule ];
+
+ var ruleMessage = isObject( ruleMessageOrData ) ? ruleMessageOrData.message :
+ ( isString( ruleMessageOrData ) ? ruleMessageOrData : undefined );
+
+ var ruleInput = isObject( ruleMessageOrData ) && ruleMessageOrData.message ? ruleMessageOrData.input :
+ ( isString( ruleMessageOrData ) ? undefined : ruleMessageOrData );
+
+ var validator = this.parseRule( rule, field, database, getAlias, ruleMessage || message, ruleInput );
+
+ validators.push( validator );
+ }
+ }
+
+ return validators;
+ },
+
+ parseRule: function(rule, field, database, getAlias, message, input)
+ {
+ var colon = rule.indexOf( this.RuleSeparator );
+ var ruleName = colon === -1 ? rule : rule.substring( 0, colon );
+
+ if ( ruleName.charAt( 0 ) === '$' )
+ {
+ return this.customValidator( ruleName, field, database, getAlias, message );
+ }
+
+ var ruleParams = colon === -1 ? input : rule.substring( colon + 1 );
+ var validatorFactory = Validation.Rules[ ruleName ];
+
+ if ( !validatorFactory )
+ {
+ throw ruleName + ' is not a valid rule';
+ }
+
+ return validatorFactory( field, ruleParams, database, getAlias, message );
+ },
+
+ parseExpression: function(expr, database)
+ {
+ var parsers = Validation.Expressions;
+
+ for (var i = 0; i < parsers.length; i++)
+ {
+ var parser = parsers[ i ];
+ var expressionFunction = parser( expr, database );
+
+ if ( isFunction( expressionFunction ) )
+ {
+ return expressionFunction; // (value, model)
+ }
+ }
+
+ return noop;
+ },
+
+ customValidator: function(functionName, field, database, getAlias, message)
+ {
+ return function(value, model, setMessage)
+ {
+ var result = model[ functionName ]( value, getAlias, message );
+
+ if ( isString( result ) )
+ {
+ setMessage( result );
+ }
+
+ return value;
+ };
+ }
+};
+
+Validation.Expression.date =
+Validation.Expressions.push(function(expr, database)
+{
+ var parsed = parseDate( expr );
+
+ if ( parsed !== false )
+ {
+ var parsedTime = parsed.getTime();
+
+ return function(value, model)
+ {
+ return parsedTime;
+ };
+ }
+}) - 1;
+
+Validation.Expression.field =
+Validation.Expressions.push(function(expr, database)
+{
+ if ( indexOf( database.fields, expr ) )
+ {
+ return function(value, model)
+ {
+ return model.$get( expr );
+ };
+ }
+}) - 1;
+
+
+var RELATIVE_REGEX = /^([+-]\d+(\.\d+)?)\s*(.+)$/;
+
+var RELATIVE_UNITS = {
+ ms: 1,
+ millisecond: 1,
+ milliseconds: 1,
+ s: 1000,
+ second: 1000,
+ seconds: 1000,
+ min: 1000 * 60,
+ mins: 1000 * 60,
+ minute: 1000 * 60,
+ minutes: 1000 * 60,
+ hr: 1000 * 60 * 60,
+ hour: 1000 * 60 * 60,
+ hours: 1000 * 60 * 60,
+ day: 1000 * 60 * 60 * 24,
+ days: 1000 * 60 * 60 * 24,
+ wk: 1000 * 60 * 60 * 24 * 7,
+ week: 1000 * 60 * 60 * 24 * 7,
+ weeks: 1000 * 60 * 60 * 24 * 7,
+ month: ['getMonth', 'setMonth'],
+ months: ['getMonth', 'setMonth'],
+ yr: ['getFullYear', 'setFullYear'],
+ year: ['getFullYear', 'setFullYear'],
+ years: ['getFullYear', 'setFullYear']
+};
+
+Validation.Expression.relative =
+Validation.Expressions.push(function(expr, database)
+{
+ var parsed = RELATIVE_REGEX.exec( expr );
+
+ if ( parsed !== null )
+ {
+ var amount = parseFloat( parsed[ 1 ] );
+ var unit = parsed[ 3 ];
+ var unitScale = RELATIVE_UNITS[ unit ];
+
+ if ( !unitScale )
+ {
+ throw unit + ' is not a valid unit.';
+ }
+
+ return function(value, model)
+ {
+ var relative = new Date();
+
+ if ( isNumber( unitScale ) )
+ {
+ relative.setTime( relative.getTime() + unitScale * amount );
+ }
+ else
+ {
+ var getter = unitScale[0];
+ var setter = unitScale[1];
+
+ relative[ setter ]( relative[ getter ]() + amount );
+ }
+
+ return relative.getTime();
+ };
+ }
+}) - 1;
+
+Validation.Expression.today =
+Validation.Expressions.push(function(expr, database)
+{
+ if ( expr === 'today' )
+ {
+ return function(value, model)
+ {
+ var today = new Date();
+
+ startOfDay( today );
+
+ return today.getTime();
+ };
+ }
+}) - 1;
+
+Validation.Expression.tomorrow =
+Validation.Expressions.push(function(expr, database)
+{
+ if ( expr === 'tomorrow' )
+ {
+ return function(value, model)
+ {
+ var tomorrow = new Date();
+
+ tomorrow.setDate( tomorrow.getDate() + 1 );
+ startOfDay( tomorrow );
+
+ return tomorrow.getTime();
+ };
+ }
+}) - 1;
+
+Validation.Expression.yesterday =
+Validation.Expressions.push(function(expr, database)
+{
+ if ( expr === 'yesterday' )
+ {
+ return function(value, model)
+ {
+ var yesterday = new Date();
+
+ yesterday.setDate( yesterday.getDate() - 1 );
+ startOfDay( yesterday );
+
+ return yesterday.getTime();
+ };
+ }
+}) - 1;
+
+// accepted
+Validation.Rules.accepted = function(field, params, database, getAlias, message)
+{
+ checkNoParams( 'accepted', field, params );
+
+ var messageTemplate = determineMessage( 'accepted', message );
+ var acceptable = Validation.Rules.accepted.acceptable;
+
+ return function(value, model, setMessage)
+ {
+ var valueString = (value + '').toLowerCase();
+ var accepted = acceptable[ valueString ];
+
+ if ( !accepted )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.accepted.message = '{$alias} has not been accepted.';
+
+Validation.Rules.accepted.acceptable =
+{
+ '1': true,
+ 'yes': true,
+ 'on': true,
+ 'y': true,
+ 'true': true
+};
+
+// contains:field,value
+collectionRuleGenerator('contains',
+ '{$alias} does not contain an item whose {$matchAlias} equals {$matchValue}.',
+ function isInvalid(value, model, matchField, matchValue, equality)
+ {
+ return !value.contains(function isMatch(m)
+ {
+ return m !== model && equality( matchValue, m.$get( matchField ) );
+ });
+ }
+);
+
+// not_contains:field,value
+collectionRuleGenerator('not_contains',
+ '{$alias} contains an item whose {$matchAlias} equals {$matchValue}.',
+ function isInvalid(value, model, matchField, matchValue, equality)
+ {
+ return value.contains(function isMatch(m)
+ {
+ return m !== model && equality( matchValue, m.$get( matchField ) );
+ });
+ }
+);
+
+function collectionRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires field & value arguments';
+ }
+
+ var matchField, matchValue, equality;
+
+ if ( isString( params ) )
+ {
+ var comma = params.indexOf(',');
+
+ if ( comma === -1 )
+ {
+ throw ruleName + ' validation rule requires field & value arguments';
+ }
+
+ matchField = params.substring( 0, comma );
+ matchValue = params.substring( comma + 1 );
+ }
+ else if ( isArray( params ) )
+ {
+ matchField = params[ 0 ];
+ matchValue = params[ 1 ];
+ equality = params[ 2 ];
+ }
+ else if ( isObject( params ) )
+ {
+ matchField = params.field;
+ matchValue = params.value;
+ equality = params.equals;
+ }
+
+ if ( !isFunction( equality ) )
+ {
+ equality = equalsCompare;
+ }
+
+ if ( indexOf( database.fields, matchField ) === -1 )
+ {
+ throw otherField + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $matchField: matchField,
+ $matchAlias: getAlias( matchField ),
+ $matchValue: matchValue
+ };
+
+ return function(value, model, setMessage)
+ {
+ if ( isInvalid( value, model, matchField, matchValue, equality ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+Validation.Rules.validate = function(field, params, database, getAlias, message)
+{
+ // message, models, validations
+ var messageOption = params || 'message';
+ var messageTemplate = determineMessage( 'validate', message );
+
+ return function(value, model, setMessage)
+ {
+ if ( isArray( value ) )
+ {
+ var invalid = new Collection();
+
+ for (var i = 0; i < value.length; i++)
+ {
+ var model = value[ i ];
+
+ if ( model && model.$validate && !model.$validate() )
+ {
+ invalid.push( model );
+ }
+ }
+
+ if ( invalid.length )
+ {
+ switch (messageOption)
+ {
+ case 'models':
+ setMessage( invalid );
+ break;
+ case 'validations':
+ setMessage( invalid.pluck( '$validations', '$$key' ) );
+ break;
+ default: // message
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ break;
+ }
+ }
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.validate.message = '{$alias} is not valid.';
+
+// after:today
+dateRuleGenerator('after',
+ '{$alias} must be after {$date}.',
+ function isInvalid(value, date) {
+ return value < endOfDay( date );
+ }
+);
+
+// after_on:tomorrow
+dateRuleGenerator('after_on',
+ '{$alias} must be after or equal to {$date}.',
+ function isInvalid(value, date) {
+ return value < date;
+ }
+);
+
+// before:yesterday
+dateRuleGenerator('before',
+ '{$alias} must be before {$date}.',
+ function isInvalid(value, date) {
+ return value > date;
+ }
+);
+
+// before_on:+2days
+dateRuleGenerator('before_on',
+ '{$alias} must be before or equal to {$date}.',
+ function isInvalid(value, date) {
+ return value > endOfDay( date );
+ }
+);
+
+// date
+ruleGenerator('date_like',
+ '{$alias} must be a valid date.',
+ function isInvalid(value, model, setValue) {
+ var parsed = parseDate( value );
+ var invalid = parsed === false;
+ if ( !invalid ) {
+ setValue( parsed.getTime() );
+ }
+ return invalid;
+ }
+);
+
+function dateRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a date expression argument';
+ }
+
+ var dateExpression;
+
+ if ( isString( params ) )
+ {
+ dateExpression = Validation.parseExpression( params, database );
+ }
+ else if ( isFunction( params ) )
+ {
+ dateExpression = params;
+ }
+ else
+ {
+ var parsed = parseDate( params );
+
+ if ( parsed !== false )
+ {
+ var parsedTime = parsed.getTime();
+
+ dateExpression = function()
+ {
+ return parsedTime;
+ };
+ }
+ }
+
+ if ( !dateExpression || dateExpression === noop )
+ {
+ throw params + ' is not a valid date expression for the ' + ruleName + ' rule';
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $date: params
+ };
+
+ return function(value, model, setMessage)
+ {
+ var parsed = parseDate( value );
+
+ if ( parsed !== false )
+ {
+ value = parsed.getTime();
+
+ var date = dateExpression( value, model );
+
+ if ( isNumber( date ) && isInvalid( value, date ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+
+// required_if:X,Y,...
+fieldListRuleGenerator('required_if',
+ '{$alias} is required.',
+ function isInvalid(value, model, field, values, map) {
+ var required = map[ model.$get( field ) ];
+
+ return required && isEmpty( value );
+ }
+);
+
+// required_unless:X,Y,...
+fieldListRuleGenerator('required_unless',
+ '{$alias} is required.',
+ function isInvalid(value, model, field, values, map) {
+ var required = !map[ model.$get( field ) ];
+
+ return required && isEmpty( value );
+ }
+);
+
+function fieldListRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a field and list arguments';
+ }
+
+ var matchField, matchValues;
+
+ if ( isString( params ) )
+ {
+ var parts = split( params, /(,)/, '\\' );
+
+ matchField = parts.shift();
+ matchValues = parts;
+ }
+ else if ( isArray( params ) )
+ {
+ matchField = params.shift();
+ matchValues = params;
+ }
+ else if ( isObject( params ) )
+ {
+ matchField = params.field;
+ matchValues = params.values;
+ }
+
+ if ( indexOf( database.fields, matchField ) === false )
+ {
+ throw matchField + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var list = joinFriendly( matchValues );
+ var extra = {
+ $params: params,
+ $matchField: matchField,
+ $matchAlias: getAlias( matchField ),
+ $list: list
+ };
+ var map = mapFromArray( matchValues, true );
+
+ return function(value, model, setMessage)
+ {
+ if ( isInvalid( value, model, matchField, matchValues, map ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+// confirmed:X
+fieldsRuleGenerator('confirmed',
+ '{$alias} must match {$fieldAliases}.',
+ function isInvalid(value, model, fields, setValue) {
+ var confirmed = true;
+
+ for (var i = 0; i < fields.length; i++)
+ {
+ if ( !equals( value, model.$get( fields[ i ] ) ) )
+ {
+ confirmed = false;
+ }
+ }
+
+ return !confirmed;
+ }
+);
+
+// different:X
+fieldsRuleGenerator('different',
+ '{$alias} must not match {$fieldAliases}.',
+ function isInvalid(value, model, fields, setValue) {
+ var different = false;
+
+ for (var i = 0; i < fields.length; i++)
+ {
+ if ( !equals( value, model.$get( fields[ i ] ) ) )
+ {
+ different = true;
+ }
+ }
+
+ return !different;
+ }
+);
+
+// if_valid:X
+fieldsRuleGenerator('if_valid',
+ '',
+ function isInvalid(value, model, fields, setValue) {
+ var valid = true;
+
+ for (var i = 0; i < fields.length && valid; i++)
+ {
+ if ( model.$validations[ fields[ i ] ] )
+ {
+ valid = false;
+ }
+ }
+
+ if ( !valid )
+ {
+ setValue( Validation.Stop );
+ }
+
+ return false;
+ }
+);
+
+// The field under validation must be present only if any of the other specified fields are present.
+// required_with:X,Y,...
+fieldsRuleGenerator('required_with',
+ '{$alias} is required.',
+ function isInvalid(value, model, fields, setValue) {
+ var required = false;
+
+ for (var i = 0; i < fields.length && !required; i++)
+ {
+ if ( !isEmpty( model.$get( fields[ i ] ) ) )
+ {
+ required = true;
+ }
+ }
+
+ return required && isEmpty( value );
+ }
+);
+
+// The field under validation must be present only if all of the other specified fields are present.
+// required_with_all:X,Y,...
+fieldsRuleGenerator('required_with_all',
+ '{$alias} is required.',
+ function isInvalid(value, model, fields, setValue) {
+ var required = true;
+
+ for (var i = 0; i < fields.length && required; i++)
+ {
+ if ( isEmpty( model.$get( fields[ i ] ) ) )
+ {
+ required = false;
+ }
+ }
+
+ return required && isEmpty( value );
+ }
+);
+
+// The field under validation must be present only when any of the other specified fields are not present.
+// required_without:X,Y,...
+fieldsRuleGenerator('required_without',
+ '{$alias} is required.',
+ function isInvalid(value, model, fields, setValue) {
+ var required = false;
+
+ for (var i = 0; i < fields.length && !required; i++)
+ {
+ if ( isEmpty( model.$get( fields[ i ] ) ) )
+ {
+ required = true;
+ }
+ }
+
+ return required && isEmpty( value );
+ }
+);
+
+// The field under validation must be present only when all of the other specified fields are not present.
+// required_without_all:X,Y,...
+fieldsRuleGenerator('required_without_all',
+ '{$alias} is required.',
+ function isInvalid(value, model, fields, setValue) {
+ var required = true;
+
+ for (var i = 0; i < fields.length && required; i++)
+ {
+ if ( !isEmpty( model.$get( fields[ i ] ) ) )
+ {
+ required = false;
+ }
+ }
+
+ return required && isEmpty( value );
+ }
+);
+
+function fieldsRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires an array of fields argument';
+ }
+
+ var fields = split( params, /(\s*,\s*)/, '\\' );
+
+ for (var i = 0; i < fields.length; i++)
+ {
+ if ( indexOf( database.fields, fields[ i ] ) === -1 )
+ {
+ throw fields[ i ] + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var fieldNames = joinFriendly( fields );
+ var fieldAliases = joinFriendly( fields, false, false, getAlias );
+ var extra = {
+ $fields: fieldNames,
+ $fieldAliases: fieldAliases
+ };
+
+ return function(value, model, setMessage)
+ {
+ function setValue( newValue )
+ {
+ value = newValue;
+ }
+
+ if ( isInvalid( value, model, fields, setValue ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+};
+
+// exists:X,Y
+foreignRuleGenerator('exists',
+ '{$alias} must match an existing {$matchAlias} in a {$class}',
+ function isInvalid(value, model, models, fieldName)
+ {
+ return !models.contains(function isDifferentMatch(m)
+ {
+ return m !== model && equals( value, m.$get( fieldName ) );
+ });
+ }
+);
+
+// unique:X,Y
+foreignRuleGenerator('unique',
+ '{$alias} must be a unique {$matchAlias} in a {$class}',
+ function isInvalid(value, model, models, fieldName)
+ {
+ return models.contains(function isDifferentMatch(m)
+ {
+ return m !== model && equals( value, m.$get( fieldName ) );
+ });
+ }
+);
+
+// 'ruleName'
+// 'ruleName:name'
+// 'ruleName:,field'
+// 'ruleName:name,field'
+// 'ruleName:name,field': '...'
+// 'ruleName': {input: {field: 'field', model: 'name'}, message: '...'}
+// 'ruleName': {input: {field: 'field', model: ModelClass}, message: '...'}
+// 'ruleName': {input: {field: 'field', models: [...]}, message: '...'}
+// 'ruleName': {field: 'field', model: 'name'}
+// 'ruleName': {field: 'field', model: ModelClass}
+// 'ruleName': {field: 'field', models: [...]}
+function foreignRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ var modelName, models, fieldName;
+
+ if ( !isValue( params ) || isString( params ) )
+ {
+ var parts = split( params || '', /(\s*,\s*)/, '\\' );
+ modelName = parts[0] || database.name;
+ fieldName = parts[1] || field;
+ models = null;
+ }
+ else if ( isArray( params ) )
+ {
+ modelName = isString( params[0] ) ? params.shift() : database.name;
+ fieldName = isString( params[0] ) ? params.shift() : field;
+ models = new ModelCollection( database, params );
+ }
+ else if ( isObject( params ) )
+ {
+ modelName = params.model || database.name;
+ fieldName = params.field || field;
+ models = params.models;
+ }
+
+ if ( !models )
+ {
+ if ( !modelName )
+ {
+ throw 'model, model class, or models is required for ' + ruleName + ' rule';
+ }
+
+ if ( isString( modelName ) )
+ {
+ Rekord.get( modelName ).success(function(modelClass)
+ {
+ models = modelClass.all();
+ });
+ }
+ else if ( isRekord( modelName ) )
+ {
+ models = modelName.all();
+ }
+ }
+
+ if ( indexOf( database.fields, fieldName ) === false )
+ {
+ throw fieldName + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $class: modelName,
+ $matchField: fieldName,
+ $matchAlias: getAlias( fieldName )
+ };
+
+ return function(value, model, setMessage)
+ {
+ if ( models && isValue( value ) )
+ {
+ if ( isInvalid( value, model, models, fieldName ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+// if:due_date:before:today|required
+
+// if all rules pass for the given field, continue with remaining rules
+subRuleGenerator('if',
+ function isInvalid(invalidCount, totalCount) {
+ return invalidCount > 0;
+ }
+);
+
+// if any rules pass for the given field, continue with remaining rules
+subRuleGenerator('if_any',
+ function isInvalid(invalidCount, totalCount) {
+ return invalidCount >= totalCount;
+ }
+);
+
+// if no rules pass for the given field, continue with remaining rules
+subRuleGenerator('if_not',
+ function isInvalid(invalidCount, totalCount) {
+ return invalidCount < totalCount;
+ }
+);
+
+
+
+function subRuleGenerator(ruleName, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a validation rule argument';
+ }
+
+ var otherField, otherRules;
+
+ if ( isString( params ) )
+ {
+ var colon = params.indexOf( ':' );
+
+ if ( colon === -1 )
+ {
+ throw params + ' is not a valid argument for the ' + ruleName + ' rule';
+ }
+
+ otherField = params.substring( 0, colon ) || field;
+ otherRules = params.substring( colon + 1 );
+ }
+ else if ( isArray( params ) )
+ {
+ otherField = params.shift() || field;
+ otherRules = params;
+ }
+ else if ( isObject( params ) )
+ {
+ otherField = params.field || field;
+ otherRules = params.rules;
+ }
+
+ if ( indexOf( database.fields, otherField ) === -1 )
+ {
+ throw otherField + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+
+ if ( !otherRules )
+ {
+ throw 'rules are required for the ' + ruleName + ' rule';
+ }
+
+ var validators = Validation.parseRules( otherRules, otherField, database, getAlias );
+
+ return function(value, model, setMessage)
+ {
+ var invalids = 0;
+
+ var setInvalid = function(message)
+ {
+ if ( message )
+ {
+ invalids++;
+ }
+ };
+
+ for (var i = 0; i < validators.length; i++)
+ {
+ validators[ i ]( value, model, setInvalid );
+ }
+
+ return isInvalid( invalids, validators.length ) ? Validation.Stop : value;
+ };
+ };
+}
+
+// in:X,Y,Z,...
+listRuleGenerator('in',
+ '{$alias} must be one of {$list}.',
+ function isInvalid(value, model, inList)
+ {
+ return !inList( value, model );
+ }
+);
+
+// not_in:X,Y,Z,...
+listRuleGenerator('not_in',
+ '{$alias} must not be one of {$list}.',
+ function isInvalid(value, model, inList)
+ {
+ return inList( value, model )
+ }
+);
+
+function listRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a list argument';
+ }
+
+ var values, inList = false;
+
+ if ( isString( params ) )
+ {
+ values = split( params, /(,)/, '\\' );
+ }
+ else if ( isArray( params ) )
+ {
+ values = params;
+ }
+ else if ( isFunction( params ) )
+ {
+ values = inList;
+ }
+
+ if ( inList !== false )
+ {
+ if ( !values || values.length === 0 )
+ {
+ throw params + ' is not a valid list of values for the ' + ruleName + ' rule';
+ }
+ }
+
+ if ( isPrimitiveArray( values ) )
+ {
+ var map = mapFromArray( values, true );
+
+ inList = function(value)
+ {
+ return map[ value ];
+ };
+ }
+ else
+ {
+ inList = function(value)
+ {
+ return indexOf( values, value, equals );
+ };
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var list = joinFriendly( values, 'or' );
+ var extra = {
+ $params: params,
+ $list: list
+ };
+
+ return function(value, model, setMessage)
+ {
+ if ( isInvalid( value, model, inList ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+// between:3,10
+rangeRuleGenerator('between', {
+ 'string': '{$alias} must have between {$start} to {$end} characters.',
+ 'number': '{$alias} must be between {$start} and {$end}.',
+ 'object': '{$alias} must have between {$start} to {$end} items.'
+ },
+ function isInvalid(value, start, end) {
+ return value < start || value > end;
+ }
+);
+
+// not_between
+rangeRuleGenerator('not_between', {
+ 'string': '{$alias} must not have between {$start} to {$end} characters.',
+ 'number': '{$alias} must not be between {$start} and {$end}.',
+ 'object': '{$alias} must not have between {$start} to {$end} items.'
+ },
+ function isInvalid(value, start, end) {
+ return value >= start && value <= end;
+ }
+);
+
+function rangeRuleGenerator(ruleName, defaultMessages, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a range argument';
+ }
+
+ var start, end;
+
+ if ( isString( params ) )
+ {
+ var range = split( params, /(\s*,\s*)/, '\\' );
+
+ start = parseFloat( range[0] );
+ end = parseFloat( range[1] );
+ }
+ else if ( isArray( params ) )
+ {
+ start = params[ 0 ];
+ end = params[ 1 ];
+ }
+ else if ( isObject( params ) )
+ {
+ start = params.start;
+ end = params.end;
+ }
+
+ if ( isNaN( start ) || isNaN( end ) )
+ {
+ throw params + ' is not a valid range of numbers for the ' + ruleName + ' rule';
+ }
+
+ if ( isString( message ) )
+ {
+ message = {
+ 'string': message,
+ 'number': message,
+ 'object': message
+ };
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $start: start,
+ $end: end
+ };
+
+ return function(value, model, setMessage)
+ {
+ var size = sizeof( value );
+ var type = typeof( value );
+ var typeMessage = messageTemplate[ type ];
+
+ if ( typeMessage && isInvalid( size, start, end ) )
+ {
+ extra.$size = size;
+
+ setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessages;
+}
+
+
+
+regexRuleGenerator('alpha',
+ '{$alias} should only contain alphabetic characters.',
+ /^[a-zA-Z]*$/
+);
+
+regexRuleGenerator('alpha_dash',
+ '{$alias} should only contain alpha-numeric characters, dashes, and underscores.',
+ /^[a-zA-Z0-9_-]*$/
+);
+
+regexRuleGenerator('alpha_num',
+ '{$alias} should only contain alpha-numeric characters.',
+ /^[a-zA-Z0-9]*$/
+);
+
+regexRuleGenerator('email',
+ '{$alias} is not a valid email.',
+ /^.+@.+\..+$/
+);
+
+regexRuleGenerator('url',
+ '{$alias} is not a valid URL.',
+ /^(https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/
+);
+
+regexRuleGenerator('uri',
+ '{$alias} is not a valid URI.',
+ /^(\w+:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/
+);
+
+regexRuleGenerator('phone',
+ '{$alias} is not a valid phone number.',
+ /^1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?$/
+);
+
+function regexRuleGenerator(ruleName, defaultMessage, regex)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ checkNoParams( ruleName, field, params );
+
+ var messageTemplate = determineMessage( ruleName, message );
+
+ return function(value, model, setMessage)
+ {
+ if ( !regex.test( value ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+Validation.Rules.regex = function(field, params, database, getAlias, message)
+{
+ var regex;
+
+ if ( isString( params ) )
+ {
+ var parsed = /^\/(.*)\/([gmi]*)$/.exec( params );
+
+ if ( parsed )
+ {
+ regex = new RegExp( parsed[1], parsed[2] );
+ }
+ }
+ else if ( isRegExp( params ) )
+ {
+ regex = params;
+ }
+
+ if ( !regex )
+ {
+ throw params + ' is not a valid regular expression for the regex rule';
+ }
+
+ var messageTemplate = determineMessage( 'regex', message );
+
+ return function(value, model, setMessage)
+ {
+ if ( !regex.test( value ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.regex.message = '{$alias} is not a valid value.';
+
+// required
+ruleGenerator('required',
+ '{$alias} is required.',
+ function isInvalid(value) {
+ return isEmpty( value );
+ }
+);
+
+// min:3
+sizeRuleGenerator('min', {
+ 'string': '{$alias} must have a minimum of {$number} characters.',
+ 'number': '{$alias} must be at least {$number}.',
+ 'object': '{$alias} must have at least {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value < number;
+ }
+);
+
+// greater_than:0
+sizeRuleGenerator('greater_than', {
+ 'string': '{$alias} must have more than {$number} characters.',
+ 'number': '{$alias} must be greater than {$number}.',
+ 'object': '{$alias} must have more than {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value <= number;
+ }
+);
+
+// max:10
+sizeRuleGenerator('max', {
+ 'string': '{$alias} must have no more than {$number} characters.',
+ 'number': '{$alias} must be no more than {$number}.',
+ 'object': '{$alias} must have no more than {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value > number;
+ }
+);
+
+// less_than:5
+sizeRuleGenerator('less_than', {
+ 'string': '{$alias} must have less than {$number} characters.',
+ 'number': '{$alias} must be less than {$number}.',
+ 'object': '{$alias} must have less than {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value >= number;
+ }
+);
+
+// equal:4.5
+sizeRuleGenerator('equal', {
+ 'string': '{$alias} must have {$number} characters.',
+ 'number': '{$alias} must equal {$number}.',
+ 'object': '{$alias} must have {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value !== number;
+ }
+);
+
+// not_equal:0
+sizeRuleGenerator('not_equal', {
+ 'string': '{$alias} must not have {$number} characters.',
+ 'number': '{$alias} must not equal {$number}.',
+ 'object': '{$alias} must not have {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value === number;
+ }
+);
+
+function sizeRuleGenerator(ruleName, defaultMessages, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ var number;
+
+ if ( isString( params ) )
+ {
+ number = parseFloat( params );
+ }
+ else if ( isNumber( params ) )
+ {
+ number = params;
+ }
+
+ if ( isNaN( number ) )
+ {
+ throw '"' + params + '" is not a valid number for the ' + ruleName + ' rule';
+ }
+
+ if ( isString( message ) )
+ {
+ message = {
+ 'string': message,
+ 'number': message,
+ 'object': message
+ };
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $number: params
+ };
+
+ return function(value, model, setMessage)
+ {
+ var size = sizeof( value );
+ var type = typeof( value );
+ var typeMessage = messageTemplate[ type ];
+
+ if ( typeMessage && isInvalid( size, number ) )
+ {
+ extra.$size = size;
+
+ setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessages;
+}
+
+
+ruleGenerator('array',
+ '{$alias} must be an array.',
+ function isInvalid(value) {
+ return !isArray( value );
+ }
+);
+
+ruleGenerator('object',
+ '{$alias} must be an object.',
+ function isInvalid(value) {
+ return !isObject( value );
+ }
+);
+
+ruleGenerator('string',
+ '{$alias} must be a string.',
+ function isInvalid(value) {
+ return !isString( value );
+ }
+);
+
+ruleGenerator('number',
+ '{$alias} must be a number.',
+ function isInvalid(value) {
+ return !isNumber( value );
+ }
+);
+
+ruleGenerator('boolean',
+ '{$alias} must be a true or false.',
+ function isInvalid(value) {
+ return !isBoolean( value );
+ }
+);
+
+ruleGenerator('model',
+ '{$alias} must have a value.',
+ function isInvalid(value) {
+ return !(value instanceof Model);
+ }
+);
+
+ruleGenerator('whole',
+ '{$alias} must be a whole number.',
+ function isInvalid(value, model, setValue) {
+ var parsed = tryParseInt( value );
+ var numeric = parseFloat( value );
+ var invalid = !isNumber( parsed );
+ if ( !invalid ) {
+ invalid = Math.floor( parsed ) !== numeric;
+ if ( !invalid ) {
+ setValue( parsed );
+ }
+ }
+ return invalid;
+ }
+);
+
+ruleGenerator('numeric',
+ '{$alias} must be numeric.',
+ function isInvalid(value, model, setValue) {
+ var parsed = tryParseFloat( value );
+ var invalid = !isNumber( parsed );
+ if ( !invalid ) {
+ setValue( parsed );
+ }
+ return invalid;
+ }
+);
+
+ruleGenerator('yesno',
+ '{$alias} must be a yes or no.',
+ function isInvalid(value, model, setValue) {
+ var mapped = Validation.Rules.yesno.map[ value ];
+ var invalid = !isBoolean( mapped );
+ if ( !invalid ) {
+ setValue( mapped );
+ }
+ return invalid;
+ }
+);
+
+Validation.Rules.yesno.map =
+{
+ 'true': true,
+ 't': true,
+ 'yes': true,
+ 'y': true,
+ '1': true,
+ 'false': false,
+ 'f': false,
+ 'no': false,
+ 'n': false,
+ '0': false
+};
+
+Validation.Rules.abs = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = Math.abs( value );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.apply = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ model.$set( field, value );
+
+ return value;
+ };
+};
+
+Validation.Rules.base64 = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ if ( global.btoa )
+ {
+ value = global.btoa( value );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.ceil = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = Math.ceil( value );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.endOfDay = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ return endOfDay( value );
+ };
+};
+
+Validation.Rules.filter = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ if ( isArray( value ) )
+ {
+ for (var i = value.length - 1; i >= 0; i--)
+ {
+ if ( !isValue( value[ i ] ) )
+ {
+ value.splice( i, 1 );
+ }
+ }
+ }
+ else if ( isObject( value ) )
+ {
+ for (var prop in value)
+ {
+ if ( !isValue( value[ prop ] ) )
+ {
+ delete value[ prop ];
+ }
+ }
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.floor = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = Math.floor( value );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.mod = function(field, params, database, alias, message)
+{
+ var number = tryParseFloat( params );
+
+ if ( !isNumber( number ) )
+ {
+ throw '"' + number + '" is not a valid number for the mod rule.';
+ }
+
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = value % number;
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.null = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ model.$set( field, null );
+
+ return null;
+ };
+};
+
+Validation.Rules.round = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = Math.round( value );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.startOfDay = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ return startOfDay( value );
+ };
+};
+
+Validation.Rules.trim = function(field, params, database, alias, message)
+{
+ // String.trim polyfill
+ if ( !String.prototype.trim )
+ {
+ var regex = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
+
+ String.prototype.trim = function()
+ {
+ return this.replace( regex, '' );
+ };
+ }
+
+ return function(value, model, setMessage)
+ {
+ if ( isString( value ) )
+ {
+ value = value.trim();
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.unbase64 = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ if ( global.atob )
+ {
+ value = global.atob( value );
+ }
+
+ return value;
+ };
+};
+
+
+
+ Rekord.Validation = Validation;
+
+ Rekord.ruleGenerator = ruleGenerator;
+ Rekord.rangeRuleGenerator = rangeRuleGenerator;
+ Rekord.collectionRuleGenerator = collectionRuleGenerator;
+ Rekord.dateRuleGenerator = dateRuleGenerator;
+ Rekord.fieldListRuleGenerator = fieldListRuleGenerator;
+ Rekord.fieldsRuleGenerator = fieldsRuleGenerator;
+ Rekord.foreignRuleGenerator = foreignRuleGenerator;
+ Rekord.subRuleGenerator = subRuleGenerator;
+ Rekord.listRuleGenerator = listRuleGenerator;
+ Rekord.regexRuleGenerator = regexRuleGenerator;
+ Rekord.sizeRuleGenerator = sizeRuleGenerator;
+
+ Rekord.joinFriendly = joinFriendly;
+ Rekord.tryParseFloat = tryParseFloat;
+ Rekord.tryParseInt = tryParseInt;
+ Rekord.startOfDay = startOfDay;
+ Rekord.endOfDay = endOfDay;
+ Rekord.determineMessage = determineMessage;
+ Rekord.mapFromArray = mapFromArray;
+ Rekord.checkNoParams = checkNoParams;
+ Rekord.generateMessage = generateMessage;
+
+})(this, Rekord);
diff --git a/build/rekord-validation.min.js b/build/rekord-validation.min.js
new file mode 100644
index 0000000..7c6553e
--- /dev/null
+++ b/build/rekord-validation.min.js
@@ -0,0 +1,2 @@
+!function(e,t,r){function n(e){var t=parseFloat(e);return isNaN(t)||(e=t),e}function a(e){var t=parseInt(e);return isNaN(t)||(e=t),e}function i(e){return V(e)?e.setHours(0,0,0,0):z(e)&&(e-=e%864e5),e}function s(e){return V(e)?e.setHours(23,59,59,999):z(e)&&(e=e-e%864e5+864e5-1),e}function u(e,t,r){B.Rules[e]=function(t,n,a,i,s){c(e,t,n);var u=o(e,s);return function(e,n,a){function s(t){e=t}return r(e,n,s)&&a(m(t,i(t),e,n,u)),e}},B.Rules[e].message=t}function o(e,t){return t||B.Rules[e].message}function l(e,t,r,n){var a=e.slice();if(n)for(var i=0;ie}),h("before","{$alias} must be before {$date}.",function(e,t){return e>t}),h("before_on","{$alias} must be before or equal to {$date}.",function(e,t){return e>s(t)}),u("date_like","{$alias} must be a valid date.",function(e,t,r){var n=W(e),a=n===!1;return a||r(n.getTime()),a}),v("required_if","{$alias} is required.",function(e,t,r,n,a){var i=a[t.$get(r)];return i&&_(e)}),v("required_unless","{$alias} is required.",function(e,t,r,n,a){var i=!a[t.$get(r)];return i&&_(e)}),$("confirmed","{$alias} must match {$fieldAliases}.",function(e,t,r,n){for(var a=!0,i=0;i0}),b("if_any",function(e,t){return e>=t}),b("if_not",function(e,t){return t>e}),p("in","{$alias} must be one of {$list}.",function(e,t,r){return!r(e,t)}),p("not_in","{$alias} must not be one of {$list}.",function(e,t,r){return r(e,t)}),R("between",{string:"{$alias} must have between {$start} to {$end} characters.",number:"{$alias} must be between {$start} and {$end}.",object:"{$alias} must have between {$start} to {$end} items."},function(e,t,r){return t>e||e>r}),R("not_between",{string:"{$alias} must not have between {$start} to {$end} characters.",number:"{$alias} must not be between {$start} and {$end}.",object:"{$alias} must not have between {$start} to {$end} items."},function(e,t,r){return e>=t&&r>=e}),w("alpha","{$alias} should only contain alphabetic characters.",/^[a-zA-Z]*$/),w("alpha_dash","{$alias} should only contain alpha-numeric characters, dashes, and underscores.",/^[a-zA-Z0-9_-]*$/),w("alpha_num","{$alias} should only contain alpha-numeric characters.",/^[a-zA-Z0-9]*$/),w("email","{$alias} is not a valid email.",/^.+@.+\..+$/),w("url","{$alias} is not a valid URL.",/^(https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)$/),w("uri","{$alias} is not a valid URI.",/^(\w+:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)$/),w("phone","{$alias} is not a valid phone number.",/^1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?$/),B.Rules.regex=function(e,t,r,n,a){var i;if(A(t)){var s=/^\/(.*)\/([gmi]*)$/.exec(t);s&&(i=new RegExp(s[1],s[2]))}else T(t)&&(i=t);if(!i)throw t+" is not a valid regular expression for the regex rule";var u=o("regex",a);return function(t,r,a){return i.test(t)||a(m(e,n(e),t,r,u)),t}},B.Rules.regex.message="{$alias} is not a valid value.",u("required","{$alias} is required.",function(e){return _(e)}),y("min",{string:"{$alias} must have a minimum of {$number} characters.",number:"{$alias} must be at least {$number}.",object:"{$alias} must have at least {$number} items."},function(e,t){return t>e}),y("greater_than",{string:"{$alias} must have more than {$number} characters.",number:"{$alias} must be greater than {$number}.",object:"{$alias} must have more than {$number} items."},function(e,t){return t>=e}),y("max",{string:"{$alias} must have no more than {$number} characters.",number:"{$alias} must be no more than {$number}.",object:"{$alias} must have no more than {$number} items."},function(e,t){return e>t}),y("less_than",{string:"{$alias} must have less than {$number} characters.",number:"{$alias} must be less than {$number}.",object:"{$alias} must have less than {$number} items."},function(e,t){return e>=t}),y("equal",{string:"{$alias} must have {$number} characters.",number:"{$alias} must equal {$number}.",object:"{$alias} must have {$number} items."},function(e,t){return e!==t}),y("not_equal",{string:"{$alias} must not have {$number} characters.",number:"{$alias} must not equal {$number}.",object:"{$alias} must not have {$number} items."},function(e,t){return e===t}),u("array","{$alias} must be an array.",function(e){return!D(e)}),u("object","{$alias} must be an object.",function(e){return!M(e)}),u("string","{$alias} must be a string.",function(e){return!A(e)}),u("number","{$alias} must be a number.",function(e){return!z(e)}),u("boolean","{$alias} must be a true or false.",function(e){return!N(e)}),u("model","{$alias} must have a value.",function(e){return!(e instanceof x)}),u("whole","{$alias} must be a whole number.",function(e,t,r){var n=a(e),i=parseFloat(e),s=!z(n);return s||(s=Math.floor(n)!==i,s||r(n)),s}),u("numeric","{$alias} must be numeric.",function(e,t,r){var a=n(e),i=!z(a);return i||r(a),i}),u("yesno","{$alias} must be a yes or no.",function(e,t,r){var n=B.Rules.yesno.map[e],a=!N(n);return a||r(n),a}),B.Rules.yesno.map={"true":!0,t:!0,yes:!0,y:!0,1:!0,"false":!1,f:!1,no:!1,n:!1,0:!1},B.Rules.abs=function(e,t,r,a,i){return function(e,t,r){return e=n(e),z(e)&&(e=Math.abs(e)),e}},B.Rules.apply=function(e,t,r,n,a){return function(t,r,n){return r.$set(e,t),t}},B.Rules.base64=function(t,r,n,a,i){return function(t,r,n){return e.btoa&&(t=e.btoa(t)),t}},B.Rules.ceil=function(e,t,r,a,i){return function(e,t,r){return e=n(e),z(e)&&(e=Math.ceil(e)),e}},B.Rules.endOfDay=function(e,t,r,n,a){return function(e,t,r){return s(e)}},B.Rules.filter=function(e,t,r,n,a){return function(e,t,r){if(D(e))for(var n=e.length-1;n>=0;n--)k(e[n])||e.splice(n,1);else if(M(e))for(var a in e)k(e[a])||delete e[a];return e}},B.Rules.floor=function(e,t,r,a,i){return function(e,t,r){return e=n(e),z(e)&&(e=Math.floor(e)),e}},B.Rules.mod=function(e,t,r,a,i){var s=n(t);if(!z(s))throw'"'+s+'" is not a valid number for the mod rule.';return function(e,t,r){return e=n(e),z(e)&&(e%=s),e}},B.Rules["null"]=function(e,t,r,n,a){return function(t,r,n){return r.$set(e,null),null}},B.Rules.round=function(e,t,r,a,i){return function(e,t,r){return e=n(e),z(e)&&(e=Math.round(e)),e}},B.Rules.startOfDay=function(e,t,r,n,a){return function(e,t,r){return i(e)}},B.Rules.trim=function(e,t,r,n,a){if(!String.prototype.trim){var i=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;String.prototype.trim=function(){return this.replace(i,"")}}return function(e,t,r){return A(e)&&(e=e.trim()),e}},B.Rules.unbase64=function(t,r,n,a,i){return function(t,r,n){return e.atob&&(t=e.atob(t)),t}},t.Validation=B,t.ruleGenerator=u,t.rangeRuleGenerator=R,t.collectionRuleGenerator=d,t.dateRuleGenerator=h,t.fieldListRuleGenerator=v,t.fieldsRuleGenerator=$,t.foreignRuleGenerator=g,t.subRuleGenerator=b,t.listRuleGenerator=p,t.regexRuleGenerator=w,t.sizeRuleGenerator=y,t.joinFriendly=l,t.tryParseFloat=n,t.tryParseInt=a,t.startOfDay=i,t.endOfDay=s,t.determineMessage=o,t.mapFromArray=f,t.checkNoParams=c,t.generateMessage=m}(this,Rekord);
+//# sourceMappingURL=rekord-validation.min.js.map
diff --git a/build/rekord-validation.min.js.map b/build/rekord-validation.min.js.map
new file mode 100644
index 0000000..bc83aac
--- /dev/null
+++ b/build/rekord-validation.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["header.js","util.js","rules/collection.js","rules/dates.js","rules/field_list.js","rules/fields.js","rules/foreign.js","rules/if.js","rules/list.js","rules/range.js","rules/regex.js","rules/sizes.js","validation.js","expressions/date.js","expressions/field.js","expressions/relative.js","expressions/today.js","expressions/tomorrow.js","expressions/yesterday.js","rules/accepted.js","rules/required.js","rules/types.js","transforms/abs.js","transforms/apply.js","transforms/base64.js","transforms/ceil.js","transforms/endOfDay.js","transforms/filter.js","transforms/floor.js","transforms/mod.js","transforms/null.js","transforms/round.js","transforms/startOfDay.js","transforms/trim.js","transforms/unbase64.js","footer.js"],"names":["global","Rekord","undefined","tryParseFloat","x","parsed","parseFloat","isNaN","tryParseInt","parseInt","startOfDay","d","isDate","setHours","isNumber","endOfDay","ruleGenerator","ruleName","defaultMessage","isInvalid","Validation","Rules","field","params","database","getAlias","message","checkNoParams","messageTemplate","determineMessage","value","model","setMessage","setValue","newValue","generateMessage","joinFriendly","arr","lastSeparator","itemSeparator","copy","slice","i","length","last","pop","join","mapFromArray","map","alias","extra","isFunction","base","$field","$alias","$value","transfer","isObject","format","collectionRuleGenerator","matchField","matchValue","equality","isString","comma","indexOf","substring","isArray","equals","equalsCompare","fields","otherField","$matchField","$matchAlias","$matchValue","dateRuleGenerator","dateExpression","parseExpression","parseDate","parsedTime","getTime","noop","$date","date","fieldListRuleGenerator","matchValues","parts","split","shift","values","list","$params","$list","fieldsRuleGenerator","fieldNames","fieldAliases","$fields","$fieldAliases","foreignRuleGenerator","modelName","models","fieldName","isValue","name","ModelCollection","get","success","modelClass","all","isRekord","$class","subRuleGenerator","otherRules","colon","rules","validators","parseRules","invalids","setInvalid","Stop","listRuleGenerator","inList","isPrimitiveArray","rangeRuleGenerator","defaultMessages","start","end","range","string","number","object","$start","$end","size","sizeof","type","typeMessage","$size","regexRuleGenerator","regex","test","sizeRuleGenerator","$number","Model","Database","Promise","Collection","isEmpty","isBoolean","isRegExp","addMethod","replaceMethod","on","Events","Plugins","db","options","aliases","validation","Defaults","messages","required","validations","prototype","$this","this","$trigger","PreValidate","$valid","$validations","$validationMessages","chain","$get","fieldValid","push","ValidatePass","ValidateFail","$init","apply","arguments","$save","$isDeleted","debug","Debugs","SAVE_DELETED","$db","resolve","$validate","Expression","Expressions","Delimiter","Escape","RuleSeparator","rule","validator","parseRule","ruleMessageOrData","ruleMessage","ruleInput","input","charAt","customValidator","ruleParams","validatorFactory","expr","parsers","parser","expressionFunction","functionName","result","RELATIVE_REGEX","RELATIVE_UNITS","ms","millisecond","milliseconds","s","second","seconds","min","mins","minute","minutes","hr","hour","hours","day","days","wk","week","weeks","month","months","yr","year","years","relative","exec","amount","unit","unitScale","Date","setTime","getter","setter","today","tomorrow","setDate","getDate","yesterday","accepted","acceptable","valueString","toLowerCase","1","yes","y","true","contains","m","validate","messageOption","invalid","pluck","confirmed","different","valid","invalidCount","totalCount","RegExp","numeric","Math","floor","mapped","yesno","t","false","f","no","n","0","abs","$set","base64","btoa","ceil","filter","splice","prop","mod","round","trim","String","replace","unbase64","atob"],"mappings":"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","file":"rekord-validation.min.js","sourcesContent":["(function(global, Rekord, undefined)\n{\n var Model = Rekord.Model;\n var Database = Rekord.Database;\n var Promise = Rekord.Promise;\n var Collection = Rekord.Collection;\n\n var isEmpty = Rekord.isEmpty;\n var isString = Rekord.isString;\n var isArray = Rekord.isArray;\n var isObject = Rekord.isObject;\n var isFunction = Rekord.isFunction;\n var isDate = Rekord.isDate;\n var isNumber = Rekord.isNumber;\n var isBoolean = Rekord.isBoolean;\n var isValue = Rekord.isValue;\n var isPrimitiveArray = Rekord.isPrimitiveArray;\n var isRegExp = Rekord.isRegExp;\n\n var noop = Rekord.noop;\n var equalsCompare = Rekord.equalsCompare;\n var equals = Rekord.equals;\n var indexOf = Rekord.indexOf;\n var sizeof = Rekord.sizeof;\n\n var split = Rekord.split;\n var transfer = Rekord.transfer;\n var format = Rekord.format;\n\n var parseDate = Rekord.parseDate;\n\n var addMethod = Rekord.addMethod;\n var replaceMethod = Rekord.replaceMethod;\n","function tryParseFloat(x)\n{\n var parsed = parseFloat( x );\n\n if ( !isNaN( parsed ) )\n {\n x = parsed;\n }\n\n return x;\n}\n\nfunction tryParseInt(x)\n{\n var parsed = parseInt( x );\n\n if ( !isNaN( parsed ) )\n {\n x = parsed;\n }\n\n return x;\n}\n\nfunction startOfDay(d)\n{\n if ( isDate( d ) )\n {\n d.setHours( 0, 0, 0, 0 );\n }\n else if ( isNumber( d ) )\n {\n d = d - (d % 86400000);\n }\n\n return d;\n}\n\nfunction endOfDay(d)\n{\n if ( isDate( d ) )\n {\n d.setHours( 23, 59, 59, 999 );\n }\n else if ( isNumber( d ) )\n {\n d = d - (d % 86400000) + 86400000 - 1;\n }\n\n return d;\n}\n\nfunction ruleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n checkNoParams( ruleName, field, params );\n\n var messageTemplate = determineMessage( ruleName, message );\n\n return function(value, model, setMessage)\n {\n function setValue( newValue )\n {\n value = newValue;\n }\n\n if ( isInvalid( value, model, setValue ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n\nfunction determineMessage(ruleName, message)\n{\n return message || Validation.Rules[ ruleName ].message;\n}\n\nfunction joinFriendly(arr, lastSeparator, itemSeparator, getAlias)\n{\n var copy = arr.slice();\n\n if ( getAlias )\n {\n for (var i = 0; i < copy.length; i++)\n {\n copy[ i ] = getAlias( copy[ i ] );\n }\n }\n\n var last = copy.pop();\n var lastSeparator = lastSeparator || 'and';\n var itemSeparator = itemSeparator || ', ';\n\n switch (copy.length) {\n case 0:\n return last;\n case 1:\n return copy[ 0 ] + ' ' + lastSeparator + ' ' + last;\n default:\n return copy.join( itemSeparator ) + itemSeparator + lastSeparator + ' ' + last;\n }\n}\n\nfunction mapFromArray(arr, value)\n{\n var map = {};\n\n for (var i = 0; i < arr.length; i++)\n {\n map[ arr[ i ] ] = value;\n }\n\n return map;\n}\n\nfunction checkNoParams(ruleName, field, params)\n{\n if ( params )\n {\n throw 'the rule ' + ruleName + ' for field ' + field + ' has no arguments';\n }\n}\n\nfunction generateMessage(field, alias, value, model, message, extra)\n{\n if ( isFunction( message ) )\n {\n message = message( field, alias, value, model, extra );\n }\n\n var base = {};\n base.$field = field;\n base.$alias = alias;\n base.$value = value;\n\n transfer( model, base );\n\n if ( isObject( extra ) )\n {\n transfer( extra, base );\n }\n\n return format( message, base );\n}\n","// contains:field,value\ncollectionRuleGenerator('contains',\n '{$alias} does not contain an item whose {$matchAlias} equals {$matchValue}.',\n function isInvalid(value, model, matchField, matchValue, equality)\n {\n return !value.contains(function isMatch(m)\n {\n return m !== model && equality( matchValue, m.$get( matchField ) );\n });\n }\n);\n\n// not_contains:field,value\ncollectionRuleGenerator('not_contains',\n '{$alias} contains an item whose {$matchAlias} equals {$matchValue}.',\n function isInvalid(value, model, matchField, matchValue, equality)\n {\n return value.contains(function isMatch(m)\n {\n return m !== model && equality( matchValue, m.$get( matchField ) );\n });\n }\n);\n\nfunction collectionRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires field & value arguments';\n }\n\n var matchField, matchValue, equality;\n\n if ( isString( params ) )\n {\n var comma = params.indexOf(',');\n\n if ( comma === -1 )\n {\n throw ruleName + ' validation rule requires field & value arguments';\n }\n\n matchField = params.substring( 0, comma );\n matchValue = params.substring( comma + 1 );\n }\n else if ( isArray( params ) )\n {\n matchField = params[ 0 ];\n matchValue = params[ 1 ];\n equality = params[ 2 ];\n }\n else if ( isObject( params ) )\n {\n matchField = params.field;\n matchValue = params.value;\n equality = params.equals;\n }\n\n if ( !isFunction( equality ) )\n {\n equality = equalsCompare;\n }\n\n if ( indexOf( database.fields, matchField ) === -1 )\n {\n throw otherField + ' is not a valid field for the ' + ruleName + ' rule';\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $matchField: matchField,\n $matchAlias: getAlias( matchField ),\n $matchValue: matchValue\n };\n\n return function(value, model, setMessage)\n {\n if ( isInvalid( value, model, matchField, matchValue, equality ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n\nValidation.Rules.validate = function(field, params, database, getAlias, message)\n{\n // message, models, validations\n var messageOption = params || 'message';\n var messageTemplate = determineMessage( 'validate', message );\n\n return function(value, model, setMessage)\n {\n if ( isArray( value ) )\n {\n var invalid = new Collection();\n\n for (var i = 0; i < value.length; i++)\n {\n var model = value[ i ];\n\n if ( model && model.$validate && !model.$validate() )\n {\n invalid.push( model );\n }\n }\n\n if ( invalid.length )\n {\n switch (messageOption)\n {\n case 'models':\n setMessage( invalid );\n break;\n case 'validations':\n setMessage( invalid.pluck( '$validations', '$$key' ) );\n break;\n default: // message\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n break;\n }\n }\n }\n\n return value;\n };\n};\n\nValidation.Rules.validate.message = '{$alias} is not valid.';\n","// after:today\ndateRuleGenerator('after',\n '{$alias} must be after {$date}.',\n function isInvalid(value, date) {\n return value < endOfDay( date );\n }\n);\n\n// after_on:tomorrow\ndateRuleGenerator('after_on',\n '{$alias} must be after or equal to {$date}.',\n function isInvalid(value, date) {\n return value < date;\n }\n);\n\n// before:yesterday\ndateRuleGenerator('before',\n '{$alias} must be before {$date}.',\n function isInvalid(value, date) {\n return value > date;\n }\n);\n\n// before_on:+2days\ndateRuleGenerator('before_on',\n '{$alias} must be before or equal to {$date}.',\n function isInvalid(value, date) {\n return value > endOfDay( date );\n }\n);\n\n// date\nruleGenerator('date_like',\n '{$alias} must be a valid date.',\n function isInvalid(value, model, setValue) {\n var parsed = parseDate( value );\n var invalid = parsed === false;\n if ( !invalid ) {\n setValue( parsed.getTime() );\n }\n return invalid;\n }\n);\n\nfunction dateRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a date expression argument';\n }\n\n var dateExpression;\n\n if ( isString( params ) )\n {\n dateExpression = Validation.parseExpression( params, database );\n }\n else if ( isFunction( params ) )\n {\n dateExpression = params;\n }\n else\n {\n var parsed = parseDate( params );\n\n if ( parsed !== false )\n {\n var parsedTime = parsed.getTime();\n\n dateExpression = function()\n {\n return parsedTime;\n };\n }\n }\n\n if ( !dateExpression || dateExpression === noop )\n {\n throw params + ' is not a valid date expression for the ' + ruleName + ' rule';\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $date: params\n };\n\n return function(value, model, setMessage)\n {\n var parsed = parseDate( value );\n\n if ( parsed !== false )\n {\n value = parsed.getTime();\n\n var date = dateExpression( value, model );\n\n if ( isNumber( date ) && isInvalid( value, date ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n","\n// required_if:X,Y,...\nfieldListRuleGenerator('required_if',\n '{$alias} is required.',\n function isInvalid(value, model, field, values, map) {\n var required = map[ model.$get( field ) ];\n\n return required && isEmpty( value );\n }\n);\n\n// required_unless:X,Y,...\nfieldListRuleGenerator('required_unless',\n '{$alias} is required.',\n function isInvalid(value, model, field, values, map) {\n var required = !map[ model.$get( field ) ];\n\n return required && isEmpty( value );\n }\n);\n\nfunction fieldListRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a field and list arguments';\n }\n\n var matchField, matchValues;\n\n if ( isString( params ) )\n {\n var parts = split( params, /(,)/, '\\\\' );\n\n matchField = parts.shift();\n matchValues = parts;\n }\n else if ( isArray( params ) )\n {\n matchField = params.shift();\n matchValues = params;\n }\n else if ( isObject( params ) )\n {\n matchField = params.field;\n matchValues = params.values;\n }\n\n if ( indexOf( database.fields, matchField ) === false )\n {\n throw matchField + ' is not a valid field for the ' + ruleName + ' rule';\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var list = joinFriendly( matchValues );\n var extra = {\n $params: params,\n $matchField: matchField,\n $matchAlias: getAlias( matchField ),\n $list: list\n };\n var map = mapFromArray( matchValues, true );\n\n return function(value, model, setMessage)\n {\n if ( isInvalid( value, model, matchField, matchValues, map ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n","// confirmed:X\nfieldsRuleGenerator('confirmed',\n '{$alias} must match {$fieldAliases}.',\n function isInvalid(value, model, fields, setValue) {\n var confirmed = true;\n\n for (var i = 0; i < fields.length; i++)\n {\n if ( !equals( value, model.$get( fields[ i ] ) ) )\n {\n confirmed = false;\n }\n }\n\n return !confirmed;\n }\n);\n\n// different:X\nfieldsRuleGenerator('different',\n '{$alias} must not match {$fieldAliases}.',\n function isInvalid(value, model, fields, setValue) {\n var different = false;\n\n for (var i = 0; i < fields.length; i++)\n {\n if ( !equals( value, model.$get( fields[ i ] ) ) )\n {\n different = true;\n }\n }\n\n return !different;\n }\n);\n\n// if_valid:X\nfieldsRuleGenerator('if_valid',\n '',\n function isInvalid(value, model, fields, setValue) {\n var valid = true;\n\n for (var i = 0; i < fields.length && valid; i++)\n {\n if ( model.$validations[ fields[ i ] ] )\n {\n valid = false;\n }\n }\n\n if ( !valid )\n {\n setValue( Validation.Stop );\n }\n\n return false;\n }\n);\n\n// The field under validation must be present only if any of the other specified fields are present.\n// required_with:X,Y,...\nfieldsRuleGenerator('required_with',\n '{$alias} is required.',\n function isInvalid(value, model, fields, setValue) {\n var required = false;\n\n for (var i = 0; i < fields.length && !required; i++)\n {\n if ( !isEmpty( model.$get( fields[ i ] ) ) )\n {\n required = true;\n }\n }\n\n return required && isEmpty( value );\n }\n);\n\n// The field under validation must be present only if all of the other specified fields are present.\n// required_with_all:X,Y,...\nfieldsRuleGenerator('required_with_all',\n '{$alias} is required.',\n function isInvalid(value, model, fields, setValue) {\n var required = true;\n\n for (var i = 0; i < fields.length && required; i++)\n {\n if ( isEmpty( model.$get( fields[ i ] ) ) )\n {\n required = false;\n }\n }\n\n return required && isEmpty( value );\n }\n);\n\n// The field under validation must be present only when any of the other specified fields are not present.\n// required_without:X,Y,...\nfieldsRuleGenerator('required_without',\n '{$alias} is required.',\n function isInvalid(value, model, fields, setValue) {\n var required = false;\n\n for (var i = 0; i < fields.length && !required; i++)\n {\n if ( isEmpty( model.$get( fields[ i ] ) ) )\n {\n required = true;\n }\n }\n\n return required && isEmpty( value );\n }\n);\n\n// The field under validation must be present only when all of the other specified fields are not present.\n// required_without_all:X,Y,...\nfieldsRuleGenerator('required_without_all',\n '{$alias} is required.',\n function isInvalid(value, model, fields, setValue) {\n var required = true;\n\n for (var i = 0; i < fields.length && required; i++)\n {\n if ( !isEmpty( model.$get( fields[ i ] ) ) )\n {\n required = false;\n }\n }\n\n return required && isEmpty( value );\n }\n);\n\nfunction fieldsRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires an array of fields argument';\n }\n\n var fields = split( params, /(\\s*,\\s*)/, '\\\\' );\n\n for (var i = 0; i < fields.length; i++)\n {\n if ( indexOf( database.fields, fields[ i ] ) === -1 )\n {\n throw fields[ i ] + ' is not a valid field for the ' + ruleName + ' rule';\n }\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var fieldNames = joinFriendly( fields );\n var fieldAliases = joinFriendly( fields, false, false, getAlias );\n var extra = {\n $fields: fieldNames,\n $fieldAliases: fieldAliases\n };\n\n return function(value, model, setMessage)\n {\n function setValue( newValue )\n {\n value = newValue;\n }\n\n if ( isInvalid( value, model, fields, setValue ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n};\n","// exists:X,Y\nforeignRuleGenerator('exists',\n '{$alias} must match an existing {$matchAlias} in a {$class}',\n function isInvalid(value, model, models, fieldName)\n {\n return !models.contains(function isDifferentMatch(m)\n {\n return m !== model && equals( value, m.$get( fieldName ) );\n });\n }\n);\n\n// unique:X,Y\nforeignRuleGenerator('unique',\n '{$alias} must be a unique {$matchAlias} in a {$class}',\n function isInvalid(value, model, models, fieldName)\n {\n return models.contains(function isDifferentMatch(m)\n {\n return m !== model && equals( value, m.$get( fieldName ) );\n });\n }\n);\n\n// 'ruleName'\n// 'ruleName:name'\n// 'ruleName:,field'\n// 'ruleName:name,field'\n// 'ruleName:name,field': '...'\n// 'ruleName': {input: {field: 'field', model: 'name'}, message: '...'}\n// 'ruleName': {input: {field: 'field', model: ModelClass}, message: '...'}\n// 'ruleName': {input: {field: 'field', models: [...]}, message: '...'}\n// 'ruleName': {field: 'field', model: 'name'}\n// 'ruleName': {field: 'field', model: ModelClass}\n// 'ruleName': {field: 'field', models: [...]}\nfunction foreignRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n var modelName, models, fieldName;\n\n if ( !isValue( params ) || isString( params ) )\n {\n var parts = split( params || '', /(\\s*,\\s*)/, '\\\\' );\n modelName = parts[0] || database.name;\n fieldName = parts[1] || field;\n models = null;\n }\n else if ( isArray( params ) )\n {\n modelName = isString( params[0] ) ? params.shift() : database.name;\n fieldName = isString( params[0] ) ? params.shift() : field;\n models = new ModelCollection( database, params );\n }\n else if ( isObject( params ) )\n {\n modelName = params.model || database.name;\n fieldName = params.field || field;\n models = params.models;\n }\n\n if ( !models )\n {\n if ( !modelName )\n {\n throw 'model, model class, or models is required for ' + ruleName + ' rule';\n }\n\n if ( isString( modelName ) )\n {\n Rekord.get( modelName ).success(function(modelClass)\n {\n models = modelClass.all();\n });\n }\n else if ( isRekord( modelName ) )\n {\n models = modelName.all();\n }\n }\n\n if ( indexOf( database.fields, fieldName ) === false )\n {\n throw fieldName + ' is not a valid field for the ' + ruleName + ' rule';\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $class: modelName,\n $matchField: fieldName,\n $matchAlias: getAlias( fieldName )\n };\n\n return function(value, model, setMessage)\n {\n if ( models && isValue( value ) )\n {\n if ( isInvalid( value, model, models, fieldName ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n","// if:due_date:before:today|required\n\n// if all rules pass for the given field, continue with remaining rules\nsubRuleGenerator('if',\n function isInvalid(invalidCount, totalCount) {\n return invalidCount > 0;\n }\n);\n\n// if any rules pass for the given field, continue with remaining rules\nsubRuleGenerator('if_any',\n function isInvalid(invalidCount, totalCount) {\n return invalidCount >= totalCount;\n }\n);\n\n// if no rules pass for the given field, continue with remaining rules\nsubRuleGenerator('if_not',\n function isInvalid(invalidCount, totalCount) {\n return invalidCount < totalCount;\n }\n);\n\n\n\nfunction subRuleGenerator(ruleName, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a validation rule argument';\n }\n\n var otherField, otherRules;\n\n if ( isString( params ) )\n {\n var colon = params.indexOf( ':' );\n\n if ( colon === -1 )\n {\n throw params + ' is not a valid argument for the ' + ruleName + ' rule';\n }\n\n otherField = params.substring( 0, colon ) || field;\n otherRules = params.substring( colon + 1 );\n }\n else if ( isArray( params ) )\n {\n otherField = params.shift() || field;\n otherRules = params;\n }\n else if ( isObject( params ) )\n {\n otherField = params.field || field;\n otherRules = params.rules;\n }\n\n if ( indexOf( database.fields, otherField ) === -1 )\n {\n throw otherField + ' is not a valid field for the ' + ruleName + ' rule';\n }\n\n if ( !otherRules )\n {\n throw 'rules are required for the ' + ruleName + ' rule';\n }\n\n var validators = Validation.parseRules( otherRules, otherField, database, getAlias );\n\n return function(value, model, setMessage)\n {\n var invalids = 0;\n\n var setInvalid = function(message)\n {\n if ( message )\n {\n invalids++;\n }\n };\n\n for (var i = 0; i < validators.length; i++)\n {\n validators[ i ]( value, model, setInvalid );\n }\n\n return isInvalid( invalids, validators.length ) ? Validation.Stop : value;\n };\n };\n}\n","// in:X,Y,Z,...\nlistRuleGenerator('in',\n '{$alias} must be one of {$list}.',\n function isInvalid(value, model, inList)\n {\n return !inList( value, model );\n }\n);\n\n// not_in:X,Y,Z,...\nlistRuleGenerator('not_in',\n '{$alias} must not be one of {$list}.',\n function isInvalid(value, model, inList)\n {\n return inList( value, model )\n }\n);\n\nfunction listRuleGenerator(ruleName, defaultMessage, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a list argument';\n }\n\n var values, inList = false;\n\n if ( isString( params ) )\n {\n values = split( params, /(,)/, '\\\\' );\n }\n else if ( isArray( params ) )\n {\n values = params;\n }\n else if ( isFunction( params ) )\n {\n values = inList;\n }\n\n if ( inList !== false )\n {\n if ( !values || values.length === 0 )\n {\n throw params + ' is not a valid list of values for the ' + ruleName + ' rule';\n }\n }\n\n if ( isPrimitiveArray( values ) )\n {\n var map = mapFromArray( values, true );\n\n inList = function(value)\n {\n return map[ value ];\n };\n }\n else\n {\n inList = function(value)\n {\n return indexOf( values, value, equals );\n };\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var list = joinFriendly( values, 'or' );\n var extra = {\n $params: params,\n $list: list\n };\n\n return function(value, model, setMessage)\n {\n if ( isInvalid( value, model, inList ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );\n }\n\n return value;\n };\n };\n\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n","// between:3,10\nrangeRuleGenerator('between', {\n 'string': '{$alias} must have between {$start} to {$end} characters.',\n 'number': '{$alias} must be between {$start} and {$end}.',\n 'object': '{$alias} must have between {$start} to {$end} items.'\n },\n function isInvalid(value, start, end) {\n return value < start || value > end;\n }\n);\n\n// not_between\nrangeRuleGenerator('not_between', {\n 'string': '{$alias} must not have between {$start} to {$end} characters.',\n 'number': '{$alias} must not be between {$start} and {$end}.',\n 'object': '{$alias} must not have between {$start} to {$end} items.'\n },\n function isInvalid(value, start, end) {\n return value >= start && value <= end;\n }\n);\n\nfunction rangeRuleGenerator(ruleName, defaultMessages, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n if ( !params )\n {\n throw ruleName + ' validation rule requires a range argument';\n }\n\n var start, end;\n\n if ( isString( params ) )\n {\n var range = split( params, /(\\s*,\\s*)/, '\\\\' );\n\n start = parseFloat( range[0] );\n end = parseFloat( range[1] );\n }\n else if ( isArray( params ) )\n {\n start = params[ 0 ];\n end = params[ 1 ];\n }\n else if ( isObject( params ) )\n {\n start = params.start;\n end = params.end;\n }\n\n if ( isNaN( start ) || isNaN( end ) )\n {\n throw params + ' is not a valid range of numbers for the ' + ruleName + ' rule';\n }\n\n if ( isString( message ) )\n {\n message = {\n 'string': message,\n 'number': message,\n 'object': message\n };\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $start: start,\n $end: end\n };\n\n return function(value, model, setMessage)\n {\n var size = sizeof( value );\n var type = typeof( value );\n var typeMessage = messageTemplate[ type ];\n\n if ( typeMessage && isInvalid( size, start, end ) )\n {\n extra.$size = size;\n\n setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessages;\n}\n","\n\nregexRuleGenerator('alpha',\n '{$alias} should only contain alphabetic characters.',\n /^[a-zA-Z]*$/\n);\n\nregexRuleGenerator('alpha_dash',\n '{$alias} should only contain alpha-numeric characters, dashes, and underscores.',\n /^[a-zA-Z0-9_-]*$/\n);\n\nregexRuleGenerator('alpha_num',\n '{$alias} should only contain alpha-numeric characters.',\n /^[a-zA-Z0-9]*$/\n);\n\nregexRuleGenerator('email',\n '{$alias} is not a valid email.',\n /^.+@.+\\..+$/\n);\n\nregexRuleGenerator('url',\n '{$alias} is not a valid URL.',\n /^(https?:\\/\\/)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/\n);\n\nregexRuleGenerator('uri',\n '{$alias} is not a valid URI.',\n /^(\\w+:\\/\\/)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/\n);\n\nregexRuleGenerator('phone',\n '{$alias} is not a valid phone number.',\n /^1?\\W*([2-9][0-8][0-9])\\W*([2-9][0-9]{2})\\W*([0-9]{4})(\\se?x?t?(\\d*))?$/\n);\n\nfunction regexRuleGenerator(ruleName, defaultMessage, regex)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n checkNoParams( ruleName, field, params );\n\n var messageTemplate = determineMessage( ruleName, message );\n\n return function(value, model, setMessage)\n {\n if ( !regex.test( value ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessage;\n}\n\nValidation.Rules.regex = function(field, params, database, getAlias, message)\n{\n var regex;\n\n if ( isString( params ) )\n {\n var parsed = /^\\/(.*)\\/([gmi]*)$/.exec( params );\n\n if ( parsed )\n {\n regex = new RegExp( parsed[1], parsed[2] );\n }\n }\n else if ( isRegExp( params ) )\n {\n regex = params;\n }\n\n if ( !regex )\n {\n throw params + ' is not a valid regular expression for the regex rule';\n }\n\n var messageTemplate = determineMessage( 'regex', message );\n\n return function(value, model, setMessage)\n {\n if ( !regex.test( value ) )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n }\n\n return value;\n };\n};\n\nValidation.Rules.regex.message = '{$alias} is not a valid value.';\n","// min:3\nsizeRuleGenerator('min', {\n 'string': '{$alias} must have a minimum of {$number} characters.',\n 'number': '{$alias} must be at least {$number}.',\n 'object': '{$alias} must have at least {$number} items.'\n },\n function isInvalid(value, number) {\n return value < number;\n }\n);\n\n// greater_than:0\nsizeRuleGenerator('greater_than', {\n 'string': '{$alias} must have more than {$number} characters.',\n 'number': '{$alias} must be greater than {$number}.',\n 'object': '{$alias} must have more than {$number} items.'\n },\n function isInvalid(value, number) {\n return value <= number;\n }\n);\n\n// max:10\nsizeRuleGenerator('max', {\n 'string': '{$alias} must have no more than {$number} characters.',\n 'number': '{$alias} must be no more than {$number}.',\n 'object': '{$alias} must have no more than {$number} items.'\n },\n function isInvalid(value, number) {\n return value > number;\n }\n);\n\n// less_than:5\nsizeRuleGenerator('less_than', {\n 'string': '{$alias} must have less than {$number} characters.',\n 'number': '{$alias} must be less than {$number}.',\n 'object': '{$alias} must have less than {$number} items.'\n },\n function isInvalid(value, number) {\n return value >= number;\n }\n);\n\n// equal:4.5\nsizeRuleGenerator('equal', {\n 'string': '{$alias} must have {$number} characters.',\n 'number': '{$alias} must equal {$number}.',\n 'object': '{$alias} must have {$number} items.'\n },\n function isInvalid(value, number) {\n return value !== number;\n }\n);\n\n// not_equal:0\nsizeRuleGenerator('not_equal', {\n 'string': '{$alias} must not have {$number} characters.',\n 'number': '{$alias} must not equal {$number}.',\n 'object': '{$alias} must not have {$number} items.'\n },\n function isInvalid(value, number) {\n return value === number;\n }\n);\n\nfunction sizeRuleGenerator(ruleName, defaultMessages, isInvalid)\n{\n Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)\n {\n var number;\n\n if ( isString( params ) )\n {\n number = parseFloat( params );\n }\n else if ( isNumber( params ) )\n {\n number = params;\n }\n\n if ( isNaN( number ) )\n {\n throw '\"' + params + '\" is not a valid number for the ' + ruleName + ' rule';\n }\n\n if ( isString( message ) )\n {\n message = {\n 'string': message,\n 'number': message,\n 'object': message\n };\n }\n\n var messageTemplate = determineMessage( ruleName, message );\n var extra = {\n $number: params\n };\n\n return function(value, model, setMessage)\n {\n var size = sizeof( value );\n var type = typeof( value );\n var typeMessage = messageTemplate[ type ];\n\n if ( typeMessage && isInvalid( size, number ) )\n {\n extra.$size = size;\n\n setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) );\n }\n\n return value;\n };\n };\n\n Validation.Rules[ ruleName ].message = defaultMessages;\n}\n","Rekord.on( Rekord.Events.Plugins, function(model, db, options)\n{\n var validation = options.validation || Database.Defaults.validation;\n\n if ( isEmpty( validation ) )\n {\n return;\n }\n\n var rules = validation.rules || {};\n var messages = validation.messages || {};\n var aliases = validation.aliases || {};\n var required = !!validation.required;\n\n function getAlias(field)\n {\n return aliases[ field ] || field;\n }\n\n db.validations = {};\n\n for ( var field in rules )\n {\n db.validations[ field ] = Validation.parseRules( rules[ field ], field, db, getAlias, messages[ field ] )\n }\n\n addMethod( model.prototype, '$validate', function()\n {\n var $this = this;\n\n this.$trigger( Model.Events.PreValidate, [this] );\n\n this.$valid = true;\n this.$validations = {};\n this.$validationMessages.length = 0;\n\n for (var field in db.validations)\n {\n var chain = db.validations[ field ];\n var value = this.$get( field );\n var fieldValid = true;\n\n var setMessage = function(message)\n {\n // Only accept for the first valid message\n if ( message && fieldValid )\n {\n fieldValid = false;\n\n $this.$validations[ field ] = message;\n $this.$validationMessages.push( message );\n $this.$valid = false;\n }\n };\n\n for (var i = 0; i < chain.length && fieldValid && value !== Validation.Stop; i++)\n {\n value = chain[ i ]( value, this, setMessage );\n }\n }\n\n this.$trigger( this.$valid ? Model.Events.ValidatePass : Model.Events.ValidateFail, [this] );\n\n return this.$valid;\n });\n\n replaceMethod( model.prototype, '$init', function($init)\n {\n return function()\n {\n this.$valid = undefined;\n this.$validations = {};\n this.$validationMessages = [];\n\n return $init.apply( this, arguments );\n };\n });\n\n if ( required )\n {\n replaceMethod( model.prototype, '$save', function($save)\n {\n return function()\n {\n if ( this.$isDeleted() )\n {\n Rekord.debug( Rekord.Debugs.SAVE_DELETED, this.$db, this );\n\n return Promise.resolve( this );\n }\n\n if ( !this.$validate() )\n {\n return Promise.resolve( this );\n }\n\n return $save.apply( this, arguments );\n };\n });\n }\n});\n\nModel.Events.PreValidate = 'pre-validate';\n\nModel.Events.ValidatePass = 'validate-pass';\n\nModel.Events.ValidateFail = 'validate-fail';\n\nvar Validation =\n{\n Rules: {},\n Expression: {},\n Expressions: [],\n Delimiter: /([|])/,\n Escape: '\\\\',\n RuleSeparator: ':',\n Stop: {},\n\n parseRules: function(rules, field, database, getAlias, message)\n {\n var validators = [];\n\n if ( isString( rules ) )\n {\n rules = split( rules, this.Delimiter, this.Escape );\n }\n\n if ( isArray( rules ) )\n {\n for (var i = 0; i < rules.length; i++)\n {\n var rule = rules[ i ];\n var validator = this.parseRule( rule, field, database, getAlias, message );\n\n validators.push( validator );\n }\n }\n else if ( isObject( rules ) )\n {\n for (var rule in rules)\n {\n var ruleMessageOrData = rules[ rule ];\n\n var ruleMessage = isObject( ruleMessageOrData ) ? ruleMessageOrData.message :\n ( isString( ruleMessageOrData ) ? ruleMessageOrData : undefined );\n\n var ruleInput = isObject( ruleMessageOrData ) && ruleMessageOrData.message ? ruleMessageOrData.input :\n ( isString( ruleMessageOrData ) ? undefined : ruleMessageOrData );\n\n var validator = this.parseRule( rule, field, database, getAlias, ruleMessage || message, ruleInput );\n\n validators.push( validator );\n }\n }\n\n return validators;\n },\n\n parseRule: function(rule, field, database, getAlias, message, input)\n {\n var colon = rule.indexOf( this.RuleSeparator );\n var ruleName = colon === -1 ? rule : rule.substring( 0, colon );\n\n if ( ruleName.charAt( 0 ) === '$' )\n {\n return this.customValidator( ruleName, field, database, getAlias, message );\n }\n\n var ruleParams = colon === -1 ? input : rule.substring( colon + 1 );\n var validatorFactory = Validation.Rules[ ruleName ];\n\n if ( !validatorFactory )\n {\n throw ruleName + ' is not a valid rule';\n }\n\n return validatorFactory( field, ruleParams, database, getAlias, message );\n },\n\n parseExpression: function(expr, database)\n {\n var parsers = Validation.Expressions;\n\n for (var i = 0; i < parsers.length; i++)\n {\n var parser = parsers[ i ];\n var expressionFunction = parser( expr, database );\n\n if ( isFunction( expressionFunction ) )\n {\n return expressionFunction; // (value, model)\n }\n }\n\n return noop;\n },\n\n customValidator: function(functionName, field, database, getAlias, message)\n {\n return function(value, model, setMessage)\n {\n var result = model[ functionName ]( value, getAlias, message );\n\n if ( isString( result ) )\n {\n setMessage( result );\n }\n\n return value;\n };\n }\n};\n","Validation.Expression.date =\nValidation.Expressions.push(function(expr, database)\n{\n var parsed = parseDate( expr );\n\n if ( parsed !== false )\n {\n var parsedTime = parsed.getTime();\n\n return function(value, model)\n {\n return parsedTime;\n };\n }\n}) - 1;\n","Validation.Expression.field =\nValidation.Expressions.push(function(expr, database)\n{\n if ( indexOf( database.fields, expr ) )\n {\n return function(value, model)\n {\n return model.$get( expr );\n };\n }\n}) - 1;\n","\nvar RELATIVE_REGEX = /^([+-]\\d+(\\.\\d+)?)\\s*(.+)$/;\n\nvar RELATIVE_UNITS = {\n ms: 1,\n millisecond: 1,\n milliseconds: 1,\n s: 1000,\n second: 1000,\n seconds: 1000,\n min: 1000 * 60,\n mins: 1000 * 60,\n minute: 1000 * 60,\n minutes: 1000 * 60,\n hr: 1000 * 60 * 60,\n hour: 1000 * 60 * 60,\n hours: 1000 * 60 * 60,\n day: 1000 * 60 * 60 * 24,\n days: 1000 * 60 * 60 * 24,\n wk: 1000 * 60 * 60 * 24 * 7,\n week: 1000 * 60 * 60 * 24 * 7,\n weeks: 1000 * 60 * 60 * 24 * 7,\n month: ['getMonth', 'setMonth'],\n months: ['getMonth', 'setMonth'],\n yr: ['getFullYear', 'setFullYear'],\n year: ['getFullYear', 'setFullYear'],\n years: ['getFullYear', 'setFullYear']\n};\n\nValidation.Expression.relative =\nValidation.Expressions.push(function(expr, database)\n{\n var parsed = RELATIVE_REGEX.exec( expr );\n\n if ( parsed !== null )\n {\n var amount = parseFloat( parsed[ 1 ] );\n var unit = parsed[ 3 ];\n var unitScale = RELATIVE_UNITS[ unit ];\n\n if ( !unitScale )\n {\n throw unit + ' is not a valid unit.';\n }\n\n return function(value, model)\n {\n var relative = new Date();\n\n if ( isNumber( unitScale ) )\n {\n relative.setTime( relative.getTime() + unitScale * amount );\n }\n else\n {\n var getter = unitScale[0];\n var setter = unitScale[1];\n\n relative[ setter ]( relative[ getter ]() + amount );\n }\n\n return relative.getTime();\n };\n }\n}) - 1;\n","Validation.Expression.today =\nValidation.Expressions.push(function(expr, database)\n{\n if ( expr === 'today' )\n {\n return function(value, model)\n {\n var today = new Date();\n\n startOfDay( today );\n\n return today.getTime();\n };\n }\n}) - 1;\n","Validation.Expression.tomorrow =\nValidation.Expressions.push(function(expr, database)\n{\n if ( expr === 'tomorrow' )\n {\n return function(value, model)\n {\n var tomorrow = new Date();\n\n tomorrow.setDate( tomorrow.getDate() + 1 );\n startOfDay( tomorrow );\n\n return tomorrow.getTime();\n };\n }\n}) - 1;\n","Validation.Expression.yesterday =\nValidation.Expressions.push(function(expr, database)\n{\n if ( expr === 'yesterday' )\n {\n return function(value, model)\n {\n var yesterday = new Date();\n\n yesterday.setDate( yesterday.getDate() - 1 );\n startOfDay( yesterday );\n\n return yesterday.getTime();\n };\n }\n}) - 1;\n","// accepted\nValidation.Rules.accepted = function(field, params, database, getAlias, message)\n{\n checkNoParams( 'accepted', field, params );\n\n var messageTemplate = determineMessage( 'accepted', message );\n var acceptable = Validation.Rules.accepted.acceptable;\n\n return function(value, model, setMessage)\n {\n var valueString = (value + '').toLowerCase();\n var accepted = acceptable[ valueString ];\n\n if ( !accepted )\n {\n setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );\n }\n\n return value;\n };\n};\n\nValidation.Rules.accepted.message = '{$alias} has not been accepted.';\n\nValidation.Rules.accepted.acceptable =\n{\n '1': true,\n 'yes': true,\n 'on': true,\n 'y': true,\n 'true': true\n};\n","// required\nruleGenerator('required',\n '{$alias} is required.',\n function isInvalid(value) {\n return isEmpty( value );\n }\n);\n","\nruleGenerator('array',\n '{$alias} must be an array.',\n function isInvalid(value) {\n return !isArray( value );\n }\n);\n\nruleGenerator('object',\n '{$alias} must be an object.',\n function isInvalid(value) {\n return !isObject( value );\n }\n);\n\nruleGenerator('string',\n '{$alias} must be a string.',\n function isInvalid(value) {\n return !isString( value );\n }\n);\n\nruleGenerator('number',\n '{$alias} must be a number.',\n function isInvalid(value) {\n return !isNumber( value );\n }\n);\n\nruleGenerator('boolean',\n '{$alias} must be a true or false.',\n function isInvalid(value) {\n return !isBoolean( value );\n }\n);\n\nruleGenerator('model',\n '{$alias} must have a value.',\n function isInvalid(value) {\n return !(value instanceof Model);\n }\n);\n\nruleGenerator('whole',\n '{$alias} must be a whole number.',\n function isInvalid(value, model, setValue) {\n var parsed = tryParseInt( value );\n var numeric = parseFloat( value );\n var invalid = !isNumber( parsed );\n if ( !invalid ) {\n invalid = Math.floor( parsed ) !== numeric;\n if ( !invalid ) {\n setValue( parsed );\n }\n }\n return invalid;\n }\n);\n\nruleGenerator('numeric',\n '{$alias} must be numeric.',\n function isInvalid(value, model, setValue) {\n var parsed = tryParseFloat( value );\n var invalid = !isNumber( parsed );\n if ( !invalid ) {\n setValue( parsed );\n }\n return invalid;\n }\n);\n\nruleGenerator('yesno',\n '{$alias} must be a yes or no.',\n function isInvalid(value, model, setValue) {\n var mapped = Validation.Rules.yesno.map[ value ];\n var invalid = !isBoolean( mapped );\n if ( !invalid ) {\n setValue( mapped );\n }\n return invalid;\n }\n);\n\nValidation.Rules.yesno.map =\n{\n 'true': true,\n 't': true,\n 'yes': true,\n 'y': true,\n '1': true,\n 'false': false,\n 'f': false,\n 'no': false,\n 'n': false,\n '0': false\n};\n","Validation.Rules.abs = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n\n if ( isNumber( value ) )\n {\n value = Math.abs( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.apply = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n model.$set( field, value );\n \n return value;\n };\n};\n","Validation.Rules.base64 = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n if ( global.btoa )\n {\n value = global.btoa( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.ceil = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n \n if ( isNumber( value ) )\n {\n value = Math.ceil( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.endOfDay = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n return endOfDay( value );\n };\n};\n","Validation.Rules.filter = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n if ( isArray( value ) )\n {\n for (var i = value.length - 1; i >= 0; i--)\n {\n if ( !isValue( value[ i ] ) )\n {\n value.splice( i, 1 );\n }\n }\n }\n else if ( isObject( value ) )\n {\n for (var prop in value)\n {\n if ( !isValue( value[ prop ] ) )\n {\n delete value[ prop ];\n }\n }\n }\n\n return value;\n };\n};\n","Validation.Rules.floor = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n \n if ( isNumber( value ) )\n {\n value = Math.floor( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.mod = function(field, params, database, alias, message)\n{\n var number = tryParseFloat( params );\n\n if ( !isNumber( number ) )\n {\n throw '\"' + number + '\" is not a valid number for the mod rule.';\n }\n\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n\n if ( isNumber( value ) )\n {\n value = value % number;\n }\n\n return value;\n };\n};\n","Validation.Rules.null = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n model.$set( field, null );\n\n return null;\n };\n};\n","Validation.Rules.round = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n value = tryParseFloat( value );\n\n if ( isNumber( value ) )\n {\n value = Math.round( value );\n }\n\n return value;\n };\n};\n","Validation.Rules.startOfDay = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n return startOfDay( value );\n };\n};\n","Validation.Rules.trim = function(field, params, database, alias, message)\n{\n // String.trim polyfill\n if ( !String.prototype.trim )\n {\n var regex = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n\n String.prototype.trim = function()\n {\n return this.replace( regex, '' );\n };\n }\n\n return function(value, model, setMessage)\n {\n if ( isString( value ) )\n {\n value = value.trim();\n }\n\n return value;\n };\n};\n","Validation.Rules.unbase64 = function(field, params, database, alias, message)\n{\n return function(value, model, setMessage)\n {\n if ( global.atob )\n {\n value = global.atob( value );\n }\n\n return value;\n };\n};\n","\n\n Rekord.Validation = Validation;\n\n Rekord.ruleGenerator = ruleGenerator;\n Rekord.rangeRuleGenerator = rangeRuleGenerator;\n Rekord.collectionRuleGenerator = collectionRuleGenerator;\n Rekord.dateRuleGenerator = dateRuleGenerator;\n Rekord.fieldListRuleGenerator = fieldListRuleGenerator;\n Rekord.fieldsRuleGenerator = fieldsRuleGenerator;\n Rekord.foreignRuleGenerator = foreignRuleGenerator;\n Rekord.subRuleGenerator = subRuleGenerator;\n Rekord.listRuleGenerator = listRuleGenerator;\n Rekord.regexRuleGenerator = regexRuleGenerator;\n Rekord.sizeRuleGenerator = sizeRuleGenerator;\n\n Rekord.joinFriendly = joinFriendly;\n Rekord.tryParseFloat = tryParseFloat;\n Rekord.tryParseInt = tryParseInt;\n Rekord.startOfDay = startOfDay;\n Rekord.endOfDay = endOfDay;\n Rekord.determineMessage = determineMessage;\n Rekord.mapFromArray = mapFromArray;\n Rekord.checkNoParams = checkNoParams;\n Rekord.generateMessage = generateMessage;\n\n})(this, Rekord);\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/jsdoc.json b/jsdoc.json
new file mode 100644
index 0000000..4c3a06c
--- /dev/null
+++ b/jsdoc.json
@@ -0,0 +1,29 @@
+{
+ "source": {
+ "include": [
+ "src/lib/"
+ ]
+ },
+ "templates": {
+ "applicationName": "Rekord Validation",
+ "linenums": true,
+ "cleverLinks": true,
+ "default": {
+ "outputSourceFiles": true
+ }
+ },
+ "opts": {
+ "destination": "../rekord-validation-docs/docs/",
+ "recurse": "true",
+ "template": "./node_modules/jaguarjs-jsdoc",
+ "readme": "./README.md",
+ "pedantic": true,
+ "debug": true
+ },
+ "plugins": [
+ "plugins/markdown"
+ ],
+ "tags": {
+ "allowUnknownTags": true
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8c807d3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "rekord-validation",
+ "version": "1.0.0",
+ "description": "Advanced validation rules for rekord",
+ "author": "Philip Diffenderfer",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/Rekord/rekord-validation.git"
+ },
+ "scripts": {
+ "test": "gulp test"
+ },
+ "devDependencies": {
+ "gulp": "^3.9.0",
+ "gulp-check-filesize": "^2.0.1",
+ "gulp-concat": "^2.6.0",
+ "gulp-load-plugins": "^1.1.0",
+ "gulp-qunit": "^1.3.0",
+ "gulp-shell": "^0.5.1",
+ "gulp-sourcemaps": "^1.6.0",
+ "gulp-uglify": "^1.5.1",
+ "gulp-util": "^3.0.7",
+ "gulp-watch": "^4.3.5",
+ "jaguarjs-jsdoc": "0.0.1",
+ "jsdoc": "^3.4.0",
+ "merge-stream": "^1.0.0"
+ }
+}
diff --git a/src/footer.js b/src/footer.js
new file mode 100644
index 0000000..db96818
--- /dev/null
+++ b/src/footer.js
@@ -0,0 +1,27 @@
+
+
+ Rekord.Validation = Validation;
+
+ Rekord.ruleGenerator = ruleGenerator;
+ Rekord.rangeRuleGenerator = rangeRuleGenerator;
+ Rekord.collectionRuleGenerator = collectionRuleGenerator;
+ Rekord.dateRuleGenerator = dateRuleGenerator;
+ Rekord.fieldListRuleGenerator = fieldListRuleGenerator;
+ Rekord.fieldsRuleGenerator = fieldsRuleGenerator;
+ Rekord.foreignRuleGenerator = foreignRuleGenerator;
+ Rekord.subRuleGenerator = subRuleGenerator;
+ Rekord.listRuleGenerator = listRuleGenerator;
+ Rekord.regexRuleGenerator = regexRuleGenerator;
+ Rekord.sizeRuleGenerator = sizeRuleGenerator;
+
+ Rekord.joinFriendly = joinFriendly;
+ Rekord.tryParseFloat = tryParseFloat;
+ Rekord.tryParseInt = tryParseInt;
+ Rekord.startOfDay = startOfDay;
+ Rekord.endOfDay = endOfDay;
+ Rekord.determineMessage = determineMessage;
+ Rekord.mapFromArray = mapFromArray;
+ Rekord.checkNoParams = checkNoParams;
+ Rekord.generateMessage = generateMessage;
+
+})(this, Rekord);
diff --git a/src/header.js b/src/header.js
new file mode 100644
index 0000000..fd80764
--- /dev/null
+++ b/src/header.js
@@ -0,0 +1,33 @@
+(function(global, Rekord, undefined)
+{
+ var Model = Rekord.Model;
+ var Database = Rekord.Database;
+ var Promise = Rekord.Promise;
+ var Collection = Rekord.Collection;
+
+ var isEmpty = Rekord.isEmpty;
+ var isString = Rekord.isString;
+ var isArray = Rekord.isArray;
+ var isObject = Rekord.isObject;
+ var isFunction = Rekord.isFunction;
+ var isDate = Rekord.isDate;
+ var isNumber = Rekord.isNumber;
+ var isBoolean = Rekord.isBoolean;
+ var isValue = Rekord.isValue;
+ var isPrimitiveArray = Rekord.isPrimitiveArray;
+ var isRegExp = Rekord.isRegExp;
+
+ var noop = Rekord.noop;
+ var equalsCompare = Rekord.equalsCompare;
+ var equals = Rekord.equals;
+ var indexOf = Rekord.indexOf;
+ var sizeof = Rekord.sizeof;
+
+ var split = Rekord.split;
+ var transfer = Rekord.transfer;
+ var format = Rekord.format;
+
+ var parseDate = Rekord.parseDate;
+
+ var addMethod = Rekord.addMethod;
+ var replaceMethod = Rekord.replaceMethod;
diff --git a/src/lib/expressions/date.js b/src/lib/expressions/date.js
new file mode 100644
index 0000000..941a25a
--- /dev/null
+++ b/src/lib/expressions/date.js
@@ -0,0 +1,15 @@
+Validation.Expression.date =
+Validation.Expressions.push(function(expr, database)
+{
+ var parsed = parseDate( expr );
+
+ if ( parsed !== false )
+ {
+ var parsedTime = parsed.getTime();
+
+ return function(value, model)
+ {
+ return parsedTime;
+ };
+ }
+}) - 1;
diff --git a/src/lib/expressions/field.js b/src/lib/expressions/field.js
new file mode 100644
index 0000000..538a29d
--- /dev/null
+++ b/src/lib/expressions/field.js
@@ -0,0 +1,11 @@
+Validation.Expression.field =
+Validation.Expressions.push(function(expr, database)
+{
+ if ( indexOf( database.fields, expr ) )
+ {
+ return function(value, model)
+ {
+ return model.$get( expr );
+ };
+ }
+}) - 1;
diff --git a/src/lib/expressions/relative.js b/src/lib/expressions/relative.js
new file mode 100644
index 0000000..697e28f
--- /dev/null
+++ b/src/lib/expressions/relative.js
@@ -0,0 +1,65 @@
+
+var RELATIVE_REGEX = /^([+-]\d+(\.\d+)?)\s*(.+)$/;
+
+var RELATIVE_UNITS = {
+ ms: 1,
+ millisecond: 1,
+ milliseconds: 1,
+ s: 1000,
+ second: 1000,
+ seconds: 1000,
+ min: 1000 * 60,
+ mins: 1000 * 60,
+ minute: 1000 * 60,
+ minutes: 1000 * 60,
+ hr: 1000 * 60 * 60,
+ hour: 1000 * 60 * 60,
+ hours: 1000 * 60 * 60,
+ day: 1000 * 60 * 60 * 24,
+ days: 1000 * 60 * 60 * 24,
+ wk: 1000 * 60 * 60 * 24 * 7,
+ week: 1000 * 60 * 60 * 24 * 7,
+ weeks: 1000 * 60 * 60 * 24 * 7,
+ month: ['getMonth', 'setMonth'],
+ months: ['getMonth', 'setMonth'],
+ yr: ['getFullYear', 'setFullYear'],
+ year: ['getFullYear', 'setFullYear'],
+ years: ['getFullYear', 'setFullYear']
+};
+
+Validation.Expression.relative =
+Validation.Expressions.push(function(expr, database)
+{
+ var parsed = RELATIVE_REGEX.exec( expr );
+
+ if ( parsed !== null )
+ {
+ var amount = parseFloat( parsed[ 1 ] );
+ var unit = parsed[ 3 ];
+ var unitScale = RELATIVE_UNITS[ unit ];
+
+ if ( !unitScale )
+ {
+ throw unit + ' is not a valid unit.';
+ }
+
+ return function(value, model)
+ {
+ var relative = new Date();
+
+ if ( isNumber( unitScale ) )
+ {
+ relative.setTime( relative.getTime() + unitScale * amount );
+ }
+ else
+ {
+ var getter = unitScale[0];
+ var setter = unitScale[1];
+
+ relative[ setter ]( relative[ getter ]() + amount );
+ }
+
+ return relative.getTime();
+ };
+ }
+}) - 1;
diff --git a/src/lib/expressions/today.js b/src/lib/expressions/today.js
new file mode 100644
index 0000000..6c9c145
--- /dev/null
+++ b/src/lib/expressions/today.js
@@ -0,0 +1,15 @@
+Validation.Expression.today =
+Validation.Expressions.push(function(expr, database)
+{
+ if ( expr === 'today' )
+ {
+ return function(value, model)
+ {
+ var today = new Date();
+
+ startOfDay( today );
+
+ return today.getTime();
+ };
+ }
+}) - 1;
diff --git a/src/lib/expressions/tomorrow.js b/src/lib/expressions/tomorrow.js
new file mode 100644
index 0000000..d965b17
--- /dev/null
+++ b/src/lib/expressions/tomorrow.js
@@ -0,0 +1,16 @@
+Validation.Expression.tomorrow =
+Validation.Expressions.push(function(expr, database)
+{
+ if ( expr === 'tomorrow' )
+ {
+ return function(value, model)
+ {
+ var tomorrow = new Date();
+
+ tomorrow.setDate( tomorrow.getDate() + 1 );
+ startOfDay( tomorrow );
+
+ return tomorrow.getTime();
+ };
+ }
+}) - 1;
diff --git a/src/lib/expressions/yesterday.js b/src/lib/expressions/yesterday.js
new file mode 100644
index 0000000..512edb3
--- /dev/null
+++ b/src/lib/expressions/yesterday.js
@@ -0,0 +1,16 @@
+Validation.Expression.yesterday =
+Validation.Expressions.push(function(expr, database)
+{
+ if ( expr === 'yesterday' )
+ {
+ return function(value, model)
+ {
+ var yesterday = new Date();
+
+ yesterday.setDate( yesterday.getDate() - 1 );
+ startOfDay( yesterday );
+
+ return yesterday.getTime();
+ };
+ }
+}) - 1;
diff --git a/src/lib/rules/accepted.js b/src/lib/rules/accepted.js
new file mode 100644
index 0000000..6464c98
--- /dev/null
+++ b/src/lib/rules/accepted.js
@@ -0,0 +1,32 @@
+// accepted
+Validation.Rules.accepted = function(field, params, database, getAlias, message)
+{
+ checkNoParams( 'accepted', field, params );
+
+ var messageTemplate = determineMessage( 'accepted', message );
+ var acceptable = Validation.Rules.accepted.acceptable;
+
+ return function(value, model, setMessage)
+ {
+ var valueString = (value + '').toLowerCase();
+ var accepted = acceptable[ valueString ];
+
+ if ( !accepted )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.accepted.message = '{$alias} has not been accepted.';
+
+Validation.Rules.accepted.acceptable =
+{
+ '1': true,
+ 'yes': true,
+ 'on': true,
+ 'y': true,
+ 'true': true
+};
diff --git a/src/lib/rules/collection.js b/src/lib/rules/collection.js
new file mode 100644
index 0000000..857ccdf
--- /dev/null
+++ b/src/lib/rules/collection.js
@@ -0,0 +1,135 @@
+// contains:field,value
+collectionRuleGenerator('contains',
+ '{$alias} does not contain an item whose {$matchAlias} equals {$matchValue}.',
+ function isInvalid(value, model, matchField, matchValue, equality)
+ {
+ return !value.contains(function isMatch(m)
+ {
+ return m !== model && equality( matchValue, m.$get( matchField ) );
+ });
+ }
+);
+
+// not_contains:field,value
+collectionRuleGenerator('not_contains',
+ '{$alias} contains an item whose {$matchAlias} equals {$matchValue}.',
+ function isInvalid(value, model, matchField, matchValue, equality)
+ {
+ return value.contains(function isMatch(m)
+ {
+ return m !== model && equality( matchValue, m.$get( matchField ) );
+ });
+ }
+);
+
+function collectionRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires field & value arguments';
+ }
+
+ var matchField, matchValue, equality;
+
+ if ( isString( params ) )
+ {
+ var comma = params.indexOf(',');
+
+ if ( comma === -1 )
+ {
+ throw ruleName + ' validation rule requires field & value arguments';
+ }
+
+ matchField = params.substring( 0, comma );
+ matchValue = params.substring( comma + 1 );
+ }
+ else if ( isArray( params ) )
+ {
+ matchField = params[ 0 ];
+ matchValue = params[ 1 ];
+ equality = params[ 2 ];
+ }
+ else if ( isObject( params ) )
+ {
+ matchField = params.field;
+ matchValue = params.value;
+ equality = params.equals;
+ }
+
+ if ( !isFunction( equality ) )
+ {
+ equality = equalsCompare;
+ }
+
+ if ( indexOf( database.fields, matchField ) === -1 )
+ {
+ throw otherField + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $matchField: matchField,
+ $matchAlias: getAlias( matchField ),
+ $matchValue: matchValue
+ };
+
+ return function(value, model, setMessage)
+ {
+ if ( isInvalid( value, model, matchField, matchValue, equality ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+Validation.Rules.validate = function(field, params, database, getAlias, message)
+{
+ // message, models, validations
+ var messageOption = params || 'message';
+ var messageTemplate = determineMessage( 'validate', message );
+
+ return function(value, model, setMessage)
+ {
+ if ( isArray( value ) )
+ {
+ var invalid = new Collection();
+
+ for (var i = 0; i < value.length; i++)
+ {
+ var model = value[ i ];
+
+ if ( model && model.$validate && !model.$validate() )
+ {
+ invalid.push( model );
+ }
+ }
+
+ if ( invalid.length )
+ {
+ switch (messageOption)
+ {
+ case 'models':
+ setMessage( invalid );
+ break;
+ case 'validations':
+ setMessage( invalid.pluck( '$validations', '$$key' ) );
+ break;
+ default: // message
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ break;
+ }
+ }
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.validate.message = '{$alias} is not valid.';
diff --git a/src/lib/rules/dates.js b/src/lib/rules/dates.js
new file mode 100644
index 0000000..7980d1c
--- /dev/null
+++ b/src/lib/rules/dates.js
@@ -0,0 +1,111 @@
+// after:today
+dateRuleGenerator('after',
+ '{$alias} must be after {$date}.',
+ function isInvalid(value, date) {
+ return value < endOfDay( date );
+ }
+);
+
+// after_on:tomorrow
+dateRuleGenerator('after_on',
+ '{$alias} must be after or equal to {$date}.',
+ function isInvalid(value, date) {
+ return value < date;
+ }
+);
+
+// before:yesterday
+dateRuleGenerator('before',
+ '{$alias} must be before {$date}.',
+ function isInvalid(value, date) {
+ return value > date;
+ }
+);
+
+// before_on:+2days
+dateRuleGenerator('before_on',
+ '{$alias} must be before or equal to {$date}.',
+ function isInvalid(value, date) {
+ return value > endOfDay( date );
+ }
+);
+
+// date
+ruleGenerator('date_like',
+ '{$alias} must be a valid date.',
+ function isInvalid(value, model, setValue) {
+ var parsed = parseDate( value );
+ var invalid = parsed === false;
+ if ( !invalid ) {
+ setValue( parsed.getTime() );
+ }
+ return invalid;
+ }
+);
+
+function dateRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a date expression argument';
+ }
+
+ var dateExpression;
+
+ if ( isString( params ) )
+ {
+ dateExpression = Validation.parseExpression( params, database );
+ }
+ else if ( isFunction( params ) )
+ {
+ dateExpression = params;
+ }
+ else
+ {
+ var parsed = parseDate( params );
+
+ if ( parsed !== false )
+ {
+ var parsedTime = parsed.getTime();
+
+ dateExpression = function()
+ {
+ return parsedTime;
+ };
+ }
+ }
+
+ if ( !dateExpression || dateExpression === noop )
+ {
+ throw params + ' is not a valid date expression for the ' + ruleName + ' rule';
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $date: params
+ };
+
+ return function(value, model, setMessage)
+ {
+ var parsed = parseDate( value );
+
+ if ( parsed !== false )
+ {
+ value = parsed.getTime();
+
+ var date = dateExpression( value, model );
+
+ if ( isNumber( date ) && isInvalid( value, date ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
diff --git a/src/lib/rules/field_list.js b/src/lib/rules/field_list.js
new file mode 100644
index 0000000..d35e390
--- /dev/null
+++ b/src/lib/rules/field_list.js
@@ -0,0 +1,78 @@
+
+// required_if:X,Y,...
+fieldListRuleGenerator('required_if',
+ '{$alias} is required.',
+ function isInvalid(value, model, field, values, map) {
+ var required = map[ model.$get( field ) ];
+
+ return required && isEmpty( value );
+ }
+);
+
+// required_unless:X,Y,...
+fieldListRuleGenerator('required_unless',
+ '{$alias} is required.',
+ function isInvalid(value, model, field, values, map) {
+ var required = !map[ model.$get( field ) ];
+
+ return required && isEmpty( value );
+ }
+);
+
+function fieldListRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a field and list arguments';
+ }
+
+ var matchField, matchValues;
+
+ if ( isString( params ) )
+ {
+ var parts = split( params, /(,)/, '\\' );
+
+ matchField = parts.shift();
+ matchValues = parts;
+ }
+ else if ( isArray( params ) )
+ {
+ matchField = params.shift();
+ matchValues = params;
+ }
+ else if ( isObject( params ) )
+ {
+ matchField = params.field;
+ matchValues = params.values;
+ }
+
+ if ( indexOf( database.fields, matchField ) === false )
+ {
+ throw matchField + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var list = joinFriendly( matchValues );
+ var extra = {
+ $params: params,
+ $matchField: matchField,
+ $matchAlias: getAlias( matchField ),
+ $list: list
+ };
+ var map = mapFromArray( matchValues, true );
+
+ return function(value, model, setMessage)
+ {
+ if ( isInvalid( value, model, matchField, matchValues, map ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
diff --git a/src/lib/rules/fields.js b/src/lib/rules/fields.js
new file mode 100644
index 0000000..54f7463
--- /dev/null
+++ b/src/lib/rules/fields.js
@@ -0,0 +1,180 @@
+// confirmed:X
+fieldsRuleGenerator('confirmed',
+ '{$alias} must match {$fieldAliases}.',
+ function isInvalid(value, model, fields, setValue) {
+ var confirmed = true;
+
+ for (var i = 0; i < fields.length; i++)
+ {
+ if ( !equals( value, model.$get( fields[ i ] ) ) )
+ {
+ confirmed = false;
+ }
+ }
+
+ return !confirmed;
+ }
+);
+
+// different:X
+fieldsRuleGenerator('different',
+ '{$alias} must not match {$fieldAliases}.',
+ function isInvalid(value, model, fields, setValue) {
+ var different = false;
+
+ for (var i = 0; i < fields.length; i++)
+ {
+ if ( !equals( value, model.$get( fields[ i ] ) ) )
+ {
+ different = true;
+ }
+ }
+
+ return !different;
+ }
+);
+
+// if_valid:X
+fieldsRuleGenerator('if_valid',
+ '',
+ function isInvalid(value, model, fields, setValue) {
+ var valid = true;
+
+ for (var i = 0; i < fields.length && valid; i++)
+ {
+ if ( model.$validations[ fields[ i ] ] )
+ {
+ valid = false;
+ }
+ }
+
+ if ( !valid )
+ {
+ setValue( Validation.Stop );
+ }
+
+ return false;
+ }
+);
+
+// The field under validation must be present only if any of the other specified fields are present.
+// required_with:X,Y,...
+fieldsRuleGenerator('required_with',
+ '{$alias} is required.',
+ function isInvalid(value, model, fields, setValue) {
+ var required = false;
+
+ for (var i = 0; i < fields.length && !required; i++)
+ {
+ if ( !isEmpty( model.$get( fields[ i ] ) ) )
+ {
+ required = true;
+ }
+ }
+
+ return required && isEmpty( value );
+ }
+);
+
+// The field under validation must be present only if all of the other specified fields are present.
+// required_with_all:X,Y,...
+fieldsRuleGenerator('required_with_all',
+ '{$alias} is required.',
+ function isInvalid(value, model, fields, setValue) {
+ var required = true;
+
+ for (var i = 0; i < fields.length && required; i++)
+ {
+ if ( isEmpty( model.$get( fields[ i ] ) ) )
+ {
+ required = false;
+ }
+ }
+
+ return required && isEmpty( value );
+ }
+);
+
+// The field under validation must be present only when any of the other specified fields are not present.
+// required_without:X,Y,...
+fieldsRuleGenerator('required_without',
+ '{$alias} is required.',
+ function isInvalid(value, model, fields, setValue) {
+ var required = false;
+
+ for (var i = 0; i < fields.length && !required; i++)
+ {
+ if ( isEmpty( model.$get( fields[ i ] ) ) )
+ {
+ required = true;
+ }
+ }
+
+ return required && isEmpty( value );
+ }
+);
+
+// The field under validation must be present only when all of the other specified fields are not present.
+// required_without_all:X,Y,...
+fieldsRuleGenerator('required_without_all',
+ '{$alias} is required.',
+ function isInvalid(value, model, fields, setValue) {
+ var required = true;
+
+ for (var i = 0; i < fields.length && required; i++)
+ {
+ if ( !isEmpty( model.$get( fields[ i ] ) ) )
+ {
+ required = false;
+ }
+ }
+
+ return required && isEmpty( value );
+ }
+);
+
+function fieldsRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires an array of fields argument';
+ }
+
+ var fields = split( params, /(\s*,\s*)/, '\\' );
+
+ for (var i = 0; i < fields.length; i++)
+ {
+ if ( indexOf( database.fields, fields[ i ] ) === -1 )
+ {
+ throw fields[ i ] + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var fieldNames = joinFriendly( fields );
+ var fieldAliases = joinFriendly( fields, false, false, getAlias );
+ var extra = {
+ $fields: fieldNames,
+ $fieldAliases: fieldAliases
+ };
+
+ return function(value, model, setMessage)
+ {
+ function setValue( newValue )
+ {
+ value = newValue;
+ }
+
+ if ( isInvalid( value, model, fields, setValue ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+};
diff --git a/src/lib/rules/foreign.js b/src/lib/rules/foreign.js
new file mode 100644
index 0000000..fa4c4cb
--- /dev/null
+++ b/src/lib/rules/foreign.js
@@ -0,0 +1,109 @@
+// exists:X,Y
+foreignRuleGenerator('exists',
+ '{$alias} must match an existing {$matchAlias} in a {$class}',
+ function isInvalid(value, model, models, fieldName)
+ {
+ return !models.contains(function isDifferentMatch(m)
+ {
+ return m !== model && equals( value, m.$get( fieldName ) );
+ });
+ }
+);
+
+// unique:X,Y
+foreignRuleGenerator('unique',
+ '{$alias} must be a unique {$matchAlias} in a {$class}',
+ function isInvalid(value, model, models, fieldName)
+ {
+ return models.contains(function isDifferentMatch(m)
+ {
+ return m !== model && equals( value, m.$get( fieldName ) );
+ });
+ }
+);
+
+// 'ruleName'
+// 'ruleName:name'
+// 'ruleName:,field'
+// 'ruleName:name,field'
+// 'ruleName:name,field': '...'
+// 'ruleName': {input: {field: 'field', model: 'name'}, message: '...'}
+// 'ruleName': {input: {field: 'field', model: ModelClass}, message: '...'}
+// 'ruleName': {input: {field: 'field', models: [...]}, message: '...'}
+// 'ruleName': {field: 'field', model: 'name'}
+// 'ruleName': {field: 'field', model: ModelClass}
+// 'ruleName': {field: 'field', models: [...]}
+function foreignRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ var modelName, models, fieldName;
+
+ if ( !isValue( params ) || isString( params ) )
+ {
+ var parts = split( params || '', /(\s*,\s*)/, '\\' );
+ modelName = parts[0] || database.name;
+ fieldName = parts[1] || field;
+ models = null;
+ }
+ else if ( isArray( params ) )
+ {
+ modelName = isString( params[0] ) ? params.shift() : database.name;
+ fieldName = isString( params[0] ) ? params.shift() : field;
+ models = new ModelCollection( database, params );
+ }
+ else if ( isObject( params ) )
+ {
+ modelName = params.model || database.name;
+ fieldName = params.field || field;
+ models = params.models;
+ }
+
+ if ( !models )
+ {
+ if ( !modelName )
+ {
+ throw 'model, model class, or models is required for ' + ruleName + ' rule';
+ }
+
+ if ( isString( modelName ) )
+ {
+ Rekord.get( modelName ).success(function(modelClass)
+ {
+ models = modelClass.all();
+ });
+ }
+ else if ( isRekord( modelName ) )
+ {
+ models = modelName.all();
+ }
+ }
+
+ if ( indexOf( database.fields, fieldName ) === false )
+ {
+ throw fieldName + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $class: modelName,
+ $matchField: fieldName,
+ $matchAlias: getAlias( fieldName )
+ };
+
+ return function(value, model, setMessage)
+ {
+ if ( models && isValue( value ) )
+ {
+ if ( isInvalid( value, model, models, fieldName ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
diff --git a/src/lib/rules/if.js b/src/lib/rules/if.js
new file mode 100644
index 0000000..0c02172
--- /dev/null
+++ b/src/lib/rules/if.js
@@ -0,0 +1,92 @@
+// if:due_date:before:today|required
+
+// if all rules pass for the given field, continue with remaining rules
+subRuleGenerator('if',
+ function isInvalid(invalidCount, totalCount) {
+ return invalidCount > 0;
+ }
+);
+
+// if any rules pass for the given field, continue with remaining rules
+subRuleGenerator('if_any',
+ function isInvalid(invalidCount, totalCount) {
+ return invalidCount >= totalCount;
+ }
+);
+
+// if no rules pass for the given field, continue with remaining rules
+subRuleGenerator('if_not',
+ function isInvalid(invalidCount, totalCount) {
+ return invalidCount < totalCount;
+ }
+);
+
+
+
+function subRuleGenerator(ruleName, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a validation rule argument';
+ }
+
+ var otherField, otherRules;
+
+ if ( isString( params ) )
+ {
+ var colon = params.indexOf( ':' );
+
+ if ( colon === -1 )
+ {
+ throw params + ' is not a valid argument for the ' + ruleName + ' rule';
+ }
+
+ otherField = params.substring( 0, colon ) || field;
+ otherRules = params.substring( colon + 1 );
+ }
+ else if ( isArray( params ) )
+ {
+ otherField = params.shift() || field;
+ otherRules = params;
+ }
+ else if ( isObject( params ) )
+ {
+ otherField = params.field || field;
+ otherRules = params.rules;
+ }
+
+ if ( indexOf( database.fields, otherField ) === -1 )
+ {
+ throw otherField + ' is not a valid field for the ' + ruleName + ' rule';
+ }
+
+ if ( !otherRules )
+ {
+ throw 'rules are required for the ' + ruleName + ' rule';
+ }
+
+ var validators = Validation.parseRules( otherRules, otherField, database, getAlias );
+
+ return function(value, model, setMessage)
+ {
+ var invalids = 0;
+
+ var setInvalid = function(message)
+ {
+ if ( message )
+ {
+ invalids++;
+ }
+ };
+
+ for (var i = 0; i < validators.length; i++)
+ {
+ validators[ i ]( value, model, setInvalid );
+ }
+
+ return isInvalid( invalids, validators.length ) ? Validation.Stop : value;
+ };
+ };
+}
diff --git a/src/lib/rules/list.js b/src/lib/rules/list.js
new file mode 100644
index 0000000..5b8d796
--- /dev/null
+++ b/src/lib/rules/list.js
@@ -0,0 +1,88 @@
+// in:X,Y,Z,...
+listRuleGenerator('in',
+ '{$alias} must be one of {$list}.',
+ function isInvalid(value, model, inList)
+ {
+ return !inList( value, model );
+ }
+);
+
+// not_in:X,Y,Z,...
+listRuleGenerator('not_in',
+ '{$alias} must not be one of {$list}.',
+ function isInvalid(value, model, inList)
+ {
+ return inList( value, model )
+ }
+);
+
+function listRuleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a list argument';
+ }
+
+ var values, inList = false;
+
+ if ( isString( params ) )
+ {
+ values = split( params, /(,)/, '\\' );
+ }
+ else if ( isArray( params ) )
+ {
+ values = params;
+ }
+ else if ( isFunction( params ) )
+ {
+ values = inList;
+ }
+
+ if ( inList !== false )
+ {
+ if ( !values || values.length === 0 )
+ {
+ throw params + ' is not a valid list of values for the ' + ruleName + ' rule';
+ }
+ }
+
+ if ( isPrimitiveArray( values ) )
+ {
+ var map = mapFromArray( values, true );
+
+ inList = function(value)
+ {
+ return map[ value ];
+ };
+ }
+ else
+ {
+ inList = function(value)
+ {
+ return indexOf( values, value, equals );
+ };
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var list = joinFriendly( values, 'or' );
+ var extra = {
+ $params: params,
+ $list: list
+ };
+
+ return function(value, model, setMessage)
+ {
+ if ( isInvalid( value, model, inList ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
diff --git a/src/lib/rules/range.js b/src/lib/rules/range.js
new file mode 100644
index 0000000..8903186
--- /dev/null
+++ b/src/lib/rules/range.js
@@ -0,0 +1,90 @@
+// between:3,10
+rangeRuleGenerator('between', {
+ 'string': '{$alias} must have between {$start} to {$end} characters.',
+ 'number': '{$alias} must be between {$start} and {$end}.',
+ 'object': '{$alias} must have between {$start} to {$end} items.'
+ },
+ function isInvalid(value, start, end) {
+ return value < start || value > end;
+ }
+);
+
+// not_between
+rangeRuleGenerator('not_between', {
+ 'string': '{$alias} must not have between {$start} to {$end} characters.',
+ 'number': '{$alias} must not be between {$start} and {$end}.',
+ 'object': '{$alias} must not have between {$start} to {$end} items.'
+ },
+ function isInvalid(value, start, end) {
+ return value >= start && value <= end;
+ }
+);
+
+function rangeRuleGenerator(ruleName, defaultMessages, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ if ( !params )
+ {
+ throw ruleName + ' validation rule requires a range argument';
+ }
+
+ var start, end;
+
+ if ( isString( params ) )
+ {
+ var range = split( params, /(\s*,\s*)/, '\\' );
+
+ start = parseFloat( range[0] );
+ end = parseFloat( range[1] );
+ }
+ else if ( isArray( params ) )
+ {
+ start = params[ 0 ];
+ end = params[ 1 ];
+ }
+ else if ( isObject( params ) )
+ {
+ start = params.start;
+ end = params.end;
+ }
+
+ if ( isNaN( start ) || isNaN( end ) )
+ {
+ throw params + ' is not a valid range of numbers for the ' + ruleName + ' rule';
+ }
+
+ if ( isString( message ) )
+ {
+ message = {
+ 'string': message,
+ 'number': message,
+ 'object': message
+ };
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $start: start,
+ $end: end
+ };
+
+ return function(value, model, setMessage)
+ {
+ var size = sizeof( value );
+ var type = typeof( value );
+ var typeMessage = messageTemplate[ type ];
+
+ if ( typeMessage && isInvalid( size, start, end ) )
+ {
+ extra.$size = size;
+
+ setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessages;
+}
diff --git a/src/lib/rules/regex.js b/src/lib/rules/regex.js
new file mode 100644
index 0000000..f81620e
--- /dev/null
+++ b/src/lib/rules/regex.js
@@ -0,0 +1,96 @@
+
+
+regexRuleGenerator('alpha',
+ '{$alias} should only contain alphabetic characters.',
+ /^[a-zA-Z]*$/
+);
+
+regexRuleGenerator('alpha_dash',
+ '{$alias} should only contain alpha-numeric characters, dashes, and underscores.',
+ /^[a-zA-Z0-9_-]*$/
+);
+
+regexRuleGenerator('alpha_num',
+ '{$alias} should only contain alpha-numeric characters.',
+ /^[a-zA-Z0-9]*$/
+);
+
+regexRuleGenerator('email',
+ '{$alias} is not a valid email.',
+ /^.+@.+\..+$/
+);
+
+regexRuleGenerator('url',
+ '{$alias} is not a valid URL.',
+ /^(https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/
+);
+
+regexRuleGenerator('uri',
+ '{$alias} is not a valid URI.',
+ /^(\w+:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/
+);
+
+regexRuleGenerator('phone',
+ '{$alias} is not a valid phone number.',
+ /^1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?$/
+);
+
+function regexRuleGenerator(ruleName, defaultMessage, regex)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ checkNoParams( ruleName, field, params );
+
+ var messageTemplate = determineMessage( ruleName, message );
+
+ return function(value, model, setMessage)
+ {
+ if ( !regex.test( value ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+Validation.Rules.regex = function(field, params, database, getAlias, message)
+{
+ var regex;
+
+ if ( isString( params ) )
+ {
+ var parsed = /^\/(.*)\/([gmi]*)$/.exec( params );
+
+ if ( parsed )
+ {
+ regex = new RegExp( parsed[1], parsed[2] );
+ }
+ }
+ else if ( isRegExp( params ) )
+ {
+ regex = params;
+ }
+
+ if ( !regex )
+ {
+ throw params + ' is not a valid regular expression for the regex rule';
+ }
+
+ var messageTemplate = determineMessage( 'regex', message );
+
+ return function(value, model, setMessage)
+ {
+ if ( !regex.test( value ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ }
+
+ return value;
+ };
+};
+
+Validation.Rules.regex.message = '{$alias} is not a valid value.';
diff --git a/src/lib/rules/required.js b/src/lib/rules/required.js
new file mode 100644
index 0000000..ea66981
--- /dev/null
+++ b/src/lib/rules/required.js
@@ -0,0 +1,7 @@
+// required
+ruleGenerator('required',
+ '{$alias} is required.',
+ function isInvalid(value) {
+ return isEmpty( value );
+ }
+);
diff --git a/src/lib/rules/sizes.js b/src/lib/rules/sizes.js
new file mode 100644
index 0000000..ffc7171
--- /dev/null
+++ b/src/lib/rules/sizes.js
@@ -0,0 +1,119 @@
+// min:3
+sizeRuleGenerator('min', {
+ 'string': '{$alias} must have a minimum of {$number} characters.',
+ 'number': '{$alias} must be at least {$number}.',
+ 'object': '{$alias} must have at least {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value < number;
+ }
+);
+
+// greater_than:0
+sizeRuleGenerator('greater_than', {
+ 'string': '{$alias} must have more than {$number} characters.',
+ 'number': '{$alias} must be greater than {$number}.',
+ 'object': '{$alias} must have more than {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value <= number;
+ }
+);
+
+// max:10
+sizeRuleGenerator('max', {
+ 'string': '{$alias} must have no more than {$number} characters.',
+ 'number': '{$alias} must be no more than {$number}.',
+ 'object': '{$alias} must have no more than {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value > number;
+ }
+);
+
+// less_than:5
+sizeRuleGenerator('less_than', {
+ 'string': '{$alias} must have less than {$number} characters.',
+ 'number': '{$alias} must be less than {$number}.',
+ 'object': '{$alias} must have less than {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value >= number;
+ }
+);
+
+// equal:4.5
+sizeRuleGenerator('equal', {
+ 'string': '{$alias} must have {$number} characters.',
+ 'number': '{$alias} must equal {$number}.',
+ 'object': '{$alias} must have {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value !== number;
+ }
+);
+
+// not_equal:0
+sizeRuleGenerator('not_equal', {
+ 'string': '{$alias} must not have {$number} characters.',
+ 'number': '{$alias} must not equal {$number}.',
+ 'object': '{$alias} must not have {$number} items.'
+ },
+ function isInvalid(value, number) {
+ return value === number;
+ }
+);
+
+function sizeRuleGenerator(ruleName, defaultMessages, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ var number;
+
+ if ( isString( params ) )
+ {
+ number = parseFloat( params );
+ }
+ else if ( isNumber( params ) )
+ {
+ number = params;
+ }
+
+ if ( isNaN( number ) )
+ {
+ throw '"' + params + '" is not a valid number for the ' + ruleName + ' rule';
+ }
+
+ if ( isString( message ) )
+ {
+ message = {
+ 'string': message,
+ 'number': message,
+ 'object': message
+ };
+ }
+
+ var messageTemplate = determineMessage( ruleName, message );
+ var extra = {
+ $number: params
+ };
+
+ return function(value, model, setMessage)
+ {
+ var size = sizeof( value );
+ var type = typeof( value );
+ var typeMessage = messageTemplate[ type ];
+
+ if ( typeMessage && isInvalid( size, number ) )
+ {
+ extra.$size = size;
+
+ setMessage( generateMessage( field, getAlias( field ), value, model, typeMessage, extra ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessages;
+}
diff --git a/src/lib/rules/types.js b/src/lib/rules/types.js
new file mode 100644
index 0000000..93de498
--- /dev/null
+++ b/src/lib/rules/types.js
@@ -0,0 +1,96 @@
+
+ruleGenerator('array',
+ '{$alias} must be an array.',
+ function isInvalid(value) {
+ return !isArray( value );
+ }
+);
+
+ruleGenerator('object',
+ '{$alias} must be an object.',
+ function isInvalid(value) {
+ return !isObject( value );
+ }
+);
+
+ruleGenerator('string',
+ '{$alias} must be a string.',
+ function isInvalid(value) {
+ return !isString( value );
+ }
+);
+
+ruleGenerator('number',
+ '{$alias} must be a number.',
+ function isInvalid(value) {
+ return !isNumber( value );
+ }
+);
+
+ruleGenerator('boolean',
+ '{$alias} must be a true or false.',
+ function isInvalid(value) {
+ return !isBoolean( value );
+ }
+);
+
+ruleGenerator('model',
+ '{$alias} must have a value.',
+ function isInvalid(value) {
+ return !(value instanceof Model);
+ }
+);
+
+ruleGenerator('whole',
+ '{$alias} must be a whole number.',
+ function isInvalid(value, model, setValue) {
+ var parsed = tryParseInt( value );
+ var numeric = parseFloat( value );
+ var invalid = !isNumber( parsed );
+ if ( !invalid ) {
+ invalid = Math.floor( parsed ) !== numeric;
+ if ( !invalid ) {
+ setValue( parsed );
+ }
+ }
+ return invalid;
+ }
+);
+
+ruleGenerator('numeric',
+ '{$alias} must be numeric.',
+ function isInvalid(value, model, setValue) {
+ var parsed = tryParseFloat( value );
+ var invalid = !isNumber( parsed );
+ if ( !invalid ) {
+ setValue( parsed );
+ }
+ return invalid;
+ }
+);
+
+ruleGenerator('yesno',
+ '{$alias} must be a yes or no.',
+ function isInvalid(value, model, setValue) {
+ var mapped = Validation.Rules.yesno.map[ value ];
+ var invalid = !isBoolean( mapped );
+ if ( !invalid ) {
+ setValue( mapped );
+ }
+ return invalid;
+ }
+);
+
+Validation.Rules.yesno.map =
+{
+ 'true': true,
+ 't': true,
+ 'yes': true,
+ 'y': true,
+ '1': true,
+ 'false': false,
+ 'f': false,
+ 'no': false,
+ 'n': false,
+ '0': false
+};
diff --git a/src/lib/transforms/abs.js b/src/lib/transforms/abs.js
new file mode 100644
index 0000000..20b56aa
--- /dev/null
+++ b/src/lib/transforms/abs.js
@@ -0,0 +1,14 @@
+Validation.Rules.abs = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = Math.abs( value );
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/apply.js b/src/lib/transforms/apply.js
new file mode 100644
index 0000000..5a8dd72
--- /dev/null
+++ b/src/lib/transforms/apply.js
@@ -0,0 +1,9 @@
+Validation.Rules.apply = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ model.$set( field, value );
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/base64.js b/src/lib/transforms/base64.js
new file mode 100644
index 0000000..b173582
--- /dev/null
+++ b/src/lib/transforms/base64.js
@@ -0,0 +1,12 @@
+Validation.Rules.base64 = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ if ( global.btoa )
+ {
+ value = global.btoa( value );
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/ceil.js b/src/lib/transforms/ceil.js
new file mode 100644
index 0000000..f74324e
--- /dev/null
+++ b/src/lib/transforms/ceil.js
@@ -0,0 +1,14 @@
+Validation.Rules.ceil = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = Math.ceil( value );
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/endOfDay.js b/src/lib/transforms/endOfDay.js
new file mode 100644
index 0000000..0afca54
--- /dev/null
+++ b/src/lib/transforms/endOfDay.js
@@ -0,0 +1,7 @@
+Validation.Rules.endOfDay = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ return endOfDay( value );
+ };
+};
diff --git a/src/lib/transforms/filter.js b/src/lib/transforms/filter.js
new file mode 100644
index 0000000..52db75e
--- /dev/null
+++ b/src/lib/transforms/filter.js
@@ -0,0 +1,28 @@
+Validation.Rules.filter = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ if ( isArray( value ) )
+ {
+ for (var i = value.length - 1; i >= 0; i--)
+ {
+ if ( !isValue( value[ i ] ) )
+ {
+ value.splice( i, 1 );
+ }
+ }
+ }
+ else if ( isObject( value ) )
+ {
+ for (var prop in value)
+ {
+ if ( !isValue( value[ prop ] ) )
+ {
+ delete value[ prop ];
+ }
+ }
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/floor.js b/src/lib/transforms/floor.js
new file mode 100644
index 0000000..a978d21
--- /dev/null
+++ b/src/lib/transforms/floor.js
@@ -0,0 +1,14 @@
+Validation.Rules.floor = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = Math.floor( value );
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/mod.js b/src/lib/transforms/mod.js
new file mode 100644
index 0000000..19d7d76
--- /dev/null
+++ b/src/lib/transforms/mod.js
@@ -0,0 +1,21 @@
+Validation.Rules.mod = function(field, params, database, alias, message)
+{
+ var number = tryParseFloat( params );
+
+ if ( !isNumber( number ) )
+ {
+ throw '"' + number + '" is not a valid number for the mod rule.';
+ }
+
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = value % number;
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/null.js b/src/lib/transforms/null.js
new file mode 100644
index 0000000..16e86fd
--- /dev/null
+++ b/src/lib/transforms/null.js
@@ -0,0 +1,9 @@
+Validation.Rules.null = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ model.$set( field, null );
+
+ return null;
+ };
+};
diff --git a/src/lib/transforms/round.js b/src/lib/transforms/round.js
new file mode 100644
index 0000000..b5ecf56
--- /dev/null
+++ b/src/lib/transforms/round.js
@@ -0,0 +1,14 @@
+Validation.Rules.round = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ value = tryParseFloat( value );
+
+ if ( isNumber( value ) )
+ {
+ value = Math.round( value );
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/startOfDay.js b/src/lib/transforms/startOfDay.js
new file mode 100644
index 0000000..560ff61
--- /dev/null
+++ b/src/lib/transforms/startOfDay.js
@@ -0,0 +1,7 @@
+Validation.Rules.startOfDay = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ return startOfDay( value );
+ };
+};
diff --git a/src/lib/transforms/trim.js b/src/lib/transforms/trim.js
new file mode 100644
index 0000000..f689c99
--- /dev/null
+++ b/src/lib/transforms/trim.js
@@ -0,0 +1,23 @@
+Validation.Rules.trim = function(field, params, database, alias, message)
+{
+ // String.trim polyfill
+ if ( !String.prototype.trim )
+ {
+ var regex = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
+
+ String.prototype.trim = function()
+ {
+ return this.replace( regex, '' );
+ };
+ }
+
+ return function(value, model, setMessage)
+ {
+ if ( isString( value ) )
+ {
+ value = value.trim();
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/transforms/unbase64.js b/src/lib/transforms/unbase64.js
new file mode 100644
index 0000000..cd28603
--- /dev/null
+++ b/src/lib/transforms/unbase64.js
@@ -0,0 +1,12 @@
+Validation.Rules.unbase64 = function(field, params, database, alias, message)
+{
+ return function(value, model, setMessage)
+ {
+ if ( global.atob )
+ {
+ value = global.atob( value );
+ }
+
+ return value;
+ };
+};
diff --git a/src/lib/util.js b/src/lib/util.js
new file mode 100644
index 0000000..07a1da1
--- /dev/null
+++ b/src/lib/util.js
@@ -0,0 +1,151 @@
+function tryParseFloat(x)
+{
+ var parsed = parseFloat( x );
+
+ if ( !isNaN( parsed ) )
+ {
+ x = parsed;
+ }
+
+ return x;
+}
+
+function tryParseInt(x)
+{
+ var parsed = parseInt( x );
+
+ if ( !isNaN( parsed ) )
+ {
+ x = parsed;
+ }
+
+ return x;
+}
+
+function startOfDay(d)
+{
+ if ( isDate( d ) )
+ {
+ d.setHours( 0, 0, 0, 0 );
+ }
+ else if ( isNumber( d ) )
+ {
+ d = d - (d % 86400000);
+ }
+
+ return d;
+}
+
+function endOfDay(d)
+{
+ if ( isDate( d ) )
+ {
+ d.setHours( 23, 59, 59, 999 );
+ }
+ else if ( isNumber( d ) )
+ {
+ d = d - (d % 86400000) + 86400000 - 1;
+ }
+
+ return d;
+}
+
+function ruleGenerator(ruleName, defaultMessage, isInvalid)
+{
+ Validation.Rules[ ruleName ] = function(field, params, database, getAlias, message)
+ {
+ checkNoParams( ruleName, field, params );
+
+ var messageTemplate = determineMessage( ruleName, message );
+
+ return function(value, model, setMessage)
+ {
+ function setValue( newValue )
+ {
+ value = newValue;
+ }
+
+ if ( isInvalid( value, model, setValue ) )
+ {
+ setMessage( generateMessage( field, getAlias( field ), value, model, messageTemplate ) );
+ }
+
+ return value;
+ };
+ };
+
+ Validation.Rules[ ruleName ].message = defaultMessage;
+}
+
+function determineMessage(ruleName, message)
+{
+ return message || Validation.Rules[ ruleName ].message;
+}
+
+function joinFriendly(arr, lastSeparator, itemSeparator, getAlias)
+{
+ var copy = arr.slice();
+
+ if ( getAlias )
+ {
+ for (var i = 0; i < copy.length; i++)
+ {
+ copy[ i ] = getAlias( copy[ i ] );
+ }
+ }
+
+ var last = copy.pop();
+ var lastSeparator = lastSeparator || 'and';
+ var itemSeparator = itemSeparator || ', ';
+
+ switch (copy.length) {
+ case 0:
+ return last;
+ case 1:
+ return copy[ 0 ] + ' ' + lastSeparator + ' ' + last;
+ default:
+ return copy.join( itemSeparator ) + itemSeparator + lastSeparator + ' ' + last;
+ }
+}
+
+function mapFromArray(arr, value)
+{
+ var map = {};
+
+ for (var i = 0; i < arr.length; i++)
+ {
+ map[ arr[ i ] ] = value;
+ }
+
+ return map;
+}
+
+function checkNoParams(ruleName, field, params)
+{
+ if ( params )
+ {
+ throw 'the rule ' + ruleName + ' for field ' + field + ' has no arguments';
+ }
+}
+
+function generateMessage(field, alias, value, model, message, extra)
+{
+ if ( isFunction( message ) )
+ {
+ message = message( field, alias, value, model, extra );
+ }
+
+ var base = {};
+ base.$field = field;
+ base.$alias = alias;
+ base.$value = value;
+
+ transfer( model, base );
+
+ if ( isObject( extra ) )
+ {
+ transfer( extra, base );
+ }
+
+ return format( message, base );
+}
diff --git a/src/lib/validation.js b/src/lib/validation.js
new file mode 100644
index 0000000..8c381f1
--- /dev/null
+++ b/src/lib/validation.js
@@ -0,0 +1,212 @@
+Rekord.on( Rekord.Events.Plugins, function(model, db, options)
+{
+ var validation = options.validation || Database.Defaults.validation;
+
+ if ( isEmpty( validation ) )
+ {
+ return;
+ }
+
+ var rules = validation.rules || {};
+ var messages = validation.messages || {};
+ var aliases = validation.aliases || {};
+ var required = !!validation.required;
+
+ function getAlias(field)
+ {
+ return aliases[ field ] || field;
+ }
+
+ db.validations = {};
+
+ for ( var field in rules )
+ {
+ db.validations[ field ] = Validation.parseRules( rules[ field ], field, db, getAlias, messages[ field ] )
+ }
+
+ addMethod( model.prototype, '$validate', function()
+ {
+ var $this = this;
+
+ this.$trigger( Model.Events.PreValidate, [this] );
+
+ this.$valid = true;
+ this.$validations = {};
+ this.$validationMessages.length = 0;
+
+ for (var field in db.validations)
+ {
+ var chain = db.validations[ field ];
+ var value = this.$get( field );
+ var fieldValid = true;
+
+ var setMessage = function(message)
+ {
+ // Only accept for the first valid message
+ if ( message && fieldValid )
+ {
+ fieldValid = false;
+
+ $this.$validations[ field ] = message;
+ $this.$validationMessages.push( message );
+ $this.$valid = false;
+ }
+ };
+
+ for (var i = 0; i < chain.length && fieldValid && value !== Validation.Stop; i++)
+ {
+ value = chain[ i ]( value, this, setMessage );
+ }
+ }
+
+ this.$trigger( this.$valid ? Model.Events.ValidatePass : Model.Events.ValidateFail, [this] );
+
+ return this.$valid;
+ });
+
+ replaceMethod( model.prototype, '$init', function($init)
+ {
+ return function()
+ {
+ this.$valid = undefined;
+ this.$validations = {};
+ this.$validationMessages = [];
+
+ return $init.apply( this, arguments );
+ };
+ });
+
+ if ( required )
+ {
+ replaceMethod( model.prototype, '$save', function($save)
+ {
+ return function()
+ {
+ if ( this.$isDeleted() )
+ {
+ Rekord.debug( Rekord.Debugs.SAVE_DELETED, this.$db, this );
+
+ return Promise.resolve( this );
+ }
+
+ if ( !this.$validate() )
+ {
+ return Promise.resolve( this );
+ }
+
+ return $save.apply( this, arguments );
+ };
+ });
+ }
+});
+
+Model.Events.PreValidate = 'pre-validate';
+
+Model.Events.ValidatePass = 'validate-pass';
+
+Model.Events.ValidateFail = 'validate-fail';
+
+var Validation =
+{
+ Rules: {},
+ Expression: {},
+ Expressions: [],
+ Delimiter: /([|])/,
+ Escape: '\\',
+ RuleSeparator: ':',
+ Stop: {},
+
+ parseRules: function(rules, field, database, getAlias, message)
+ {
+ var validators = [];
+
+ if ( isString( rules ) )
+ {
+ rules = split( rules, this.Delimiter, this.Escape );
+ }
+
+ if ( isArray( rules ) )
+ {
+ for (var i = 0; i < rules.length; i++)
+ {
+ var rule = rules[ i ];
+ var validator = this.parseRule( rule, field, database, getAlias, message );
+
+ validators.push( validator );
+ }
+ }
+ else if ( isObject( rules ) )
+ {
+ for (var rule in rules)
+ {
+ var ruleMessageOrData = rules[ rule ];
+
+ var ruleMessage = isObject( ruleMessageOrData ) ? ruleMessageOrData.message :
+ ( isString( ruleMessageOrData ) ? ruleMessageOrData : undefined );
+
+ var ruleInput = isObject( ruleMessageOrData ) && ruleMessageOrData.message ? ruleMessageOrData.input :
+ ( isString( ruleMessageOrData ) ? undefined : ruleMessageOrData );
+
+ var validator = this.parseRule( rule, field, database, getAlias, ruleMessage || message, ruleInput );
+
+ validators.push( validator );
+ }
+ }
+
+ return validators;
+ },
+
+ parseRule: function(rule, field, database, getAlias, message, input)
+ {
+ var colon = rule.indexOf( this.RuleSeparator );
+ var ruleName = colon === -1 ? rule : rule.substring( 0, colon );
+
+ if ( ruleName.charAt( 0 ) === '$' )
+ {
+ return this.customValidator( ruleName, field, database, getAlias, message );
+ }
+
+ var ruleParams = colon === -1 ? input : rule.substring( colon + 1 );
+ var validatorFactory = Validation.Rules[ ruleName ];
+
+ if ( !validatorFactory )
+ {
+ throw ruleName + ' is not a valid rule';
+ }
+
+ return validatorFactory( field, ruleParams, database, getAlias, message );
+ },
+
+ parseExpression: function(expr, database)
+ {
+ var parsers = Validation.Expressions;
+
+ for (var i = 0; i < parsers.length; i++)
+ {
+ var parser = parsers[ i ];
+ var expressionFunction = parser( expr, database );
+
+ if ( isFunction( expressionFunction ) )
+ {
+ return expressionFunction; // (value, model)
+ }
+ }
+
+ return noop;
+ },
+
+ customValidator: function(functionName, field, database, getAlias, message)
+ {
+ return function(value, model, setMessage)
+ {
+ var result = model[ functionName ]( value, getAlias, message );
+
+ if ( isString( result ) )
+ {
+ setMessage( result );
+ }
+
+ return value;
+ };
+ }
+};
diff --git a/test/base.js b/test/base.js
new file mode 100644
index 0000000..d2267db
--- /dev/null
+++ b/test/base.js
@@ -0,0 +1,521 @@
+
+// Configuration
+
+Rekord.autoload = true;
+Rekord.Database.Defaults.load = Rekord.Load.All;
+
+QUnit.config.reorder = false;
+
+// Extra Assertions
+
+function isInstance(model, Class, message)
+{
+ ok( model instanceof Class, message );
+}
+
+function isType(value, type, message)
+{
+ strictEqual( typeof value, type, message );
+}
+
+function hasModel(rekord, key, model, message)
+{
+ strictEqual( rekord.get( key ), model, message );
+}
+
+function pushChanges(target, changes)
+{
+ var previous = {};
+
+ for (var prop in changes)
+ {
+ previous[ prop ] = target[ prop ];
+ target[ prop ] = changes[ prop ];
+ }
+
+ return function()
+ {
+ Rekord.transfer( previous, target );
+ };
+};
+
+function currentTime()
+{
+ var counter = 1;
+
+ return function newDefaultTime()
+ {
+ return counter++;
+ };
+}
+
+function currentDate()
+{
+ var start = new Date();
+
+ return function newDefaultDate()
+ {
+ start.setDate( start.getDate() + 1 );
+
+ return new Date( start.getTime() );
+ };
+}
+
+// Extending Assert
+
+QUnit.assert.timer = function()
+{
+ return QUnit.assert.currentTimer = new TestTimer();
+};
+
+function TestTimer()
+{
+ this.callbacks = [];
+ this.time = 0;
+}
+
+TestTimer.prototype =
+{
+ wait: function(millis, func)
+ {
+ var callbacks = this.callbacks;
+ var at = millis + this.time;
+
+ if ( callbacks[ at ] )
+ {
+ callbacks[ at ].push( func );
+ }
+ else
+ {
+ callbacks[ at ] = [ func ];
+ }
+ },
+ run: function()
+ {
+ var callbacks = this.callbacks;
+
+ for (var i = 0; i < callbacks.length; i++)
+ {
+ var calls = callbacks[ i ];
+
+ this.time = i;
+
+ if ( calls )
+ {
+ for (var k = 0; k < calls.length; k++)
+ {
+ calls[ k ]();
+ }
+ }
+ }
+ }
+};
+
+function wait(millis, func)
+{
+ QUnit.assert.currentTimer.wait( millis, func );
+}
+
+// Mock File
+
+function TestFile(name, size, type, contents)
+{
+ this.name = name;
+ this.size = size;
+ this.type = type;
+ this.contents = contents;
+}
+
+function TestFileReader()
+{
+ this.onload = Rekord.noop;
+}
+
+TestFileReader.prototype =
+{
+ readAsText: function(file)
+ {
+ if ( TestFileReader.IMMEDIATE )
+ {
+ this.readAsTextNow( file );
+ }
+ else
+ {
+ TestFileReader.pending.push( [this, 'readAsTextNow', file] );
+ }
+ },
+ readAsTextNow: function(file)
+ {
+ this.onload( {target:{result: file.contents}} );
+ },
+ readAsDataURL: function(file)
+ {
+ if ( TestFileReader.IMMEDIATE )
+ {
+ this.readAsDataURLNow( file );
+ }
+ else
+ {
+ TestFileReader.pending.push( [this, 'readAsDataURLNow', file] );
+ }
+ },
+ readAsDataURLNow: function(file)
+ {
+ this.onload( {target:{result: 'data:text/plain;base64,' + btoa(file.contents)}} );
+ }
+};
+
+TestFileReader.IMMEDIATE = true;
+TestFileReader.pending = [];
+
+TestFileReader.executePending = function()
+{
+ for (var i = 0; i < TestFileReader.pending.length; i++)
+ {
+ var pair = TestFileReader.pending[ i ];
+
+ pair[ 0 ][ pair[ 1 ] ]( pair[ 2 ] );
+ }
+
+ TestFileReader.pending.length = 0;
+};
+
+window.File = TestFile;
+window.FileList = Array;
+window.FileReader = TestFileReader;
+
+// Utility Methods
+
+function offline()
+{
+ Rekord.setOffline();
+ Rekord.forceOffline = true;
+}
+
+function online()
+{
+ Rekord.forceOffline = false;
+ Rekord.setOnline();
+}
+
+function noline()
+{
+ Rekord.off( 'online offline' );
+ online();
+}
+
+function restart()
+{
+ Rekord.promises = {};
+ noline();
+ online();
+}
+
+// Rekord.store."database name".(put|remove|all)
+
+Rekord.store = function(database)
+{
+ var store = Rekord.store[ database.name ];
+
+ if ( !store )
+ {
+ store = Rekord.store[ database.name ] = new TestStore();
+ }
+
+ return store;
+};
+
+function TestStore()
+{
+ this.map = new Rekord.Map();
+ this.valid = true;
+ this.delay = 0;
+ this.lastKey = this.lastRecord = this.lastOperation = null;
+}
+
+TestStore.prototype =
+{
+ finishDelayed: function(success, failure, arg0, arg1)
+ {
+ var store = this;
+
+ if ( store.delay > 0 )
+ {
+ wait( store.delay, function()
+ {
+ store.finish( success, failure, arg0, arg1 );
+
+ });
+ }
+ else
+ {
+ store.finish( success, failure, arg0, arg1 );
+ }
+ },
+ finish: function(success, failure, arg0, arg1)
+ {
+ if ( this.valid )
+ {
+ if ( success ) success( arg0, arg1 );
+ }
+ else
+ {
+ if ( failure ) failure( arg0, arg1 );
+ }
+ },
+ get: function(key, success, failure)
+ {
+ this.lastKey = key;
+ this.lastOperation = 'get';
+
+ var map = this.map;
+ function onGet()
+ {
+ var model = map.get( key );
+ if ( model ) {
+ success.call( this, key, model );
+ } else {
+ failure.apply( this, 404 );
+ }
+ }
+
+ this.finishDelayed( onGet, failure );
+ },
+ save: function(model, success, failure)
+ {
+ this.put( model.$key(), model, success, failure );
+ },
+ put: function(key, record, success, failure)
+ {
+ this.lastKey = key;
+ this.lastRecord = record;
+ this.lastOperation = 'put';
+
+ var map = this.map;
+ function onPut()
+ {
+ map.put( key, record );
+ success.apply( this, arguments );
+ }
+
+ this.finishDelayed( onPut, failure, key, record );
+ },
+ remove: function(key, success, failure)
+ {
+ this.lastKey = key;
+ this.lastOperation = 'remove';
+
+ var map = this.map;
+ var removed = map.get( key );
+ function onRemove()
+ {
+ map.remove( key );
+ success.apply( this, arguments );
+ }
+
+ this.finishDelayed( onRemove, failure, key, removed );
+ },
+ all: function(success, failure)
+ {
+ this.lastOperation = 'all';
+ this.finishDelayed( success, failure, this.map.values, this.map.keys );
+ }
+};
+
+
+// Rekord.live."database name".(save|remove)
+
+Rekord.live = function(database)
+{
+ var live = Rekord.live[ database.name ];
+
+ if ( !live )
+ {
+ live = Rekord.live[ database.name ] = new TestLive( database );
+ }
+
+ return live;
+};
+
+function TestLive(database)
+{
+ this.database = database;
+ this.onHandleMessage = null;
+ this.lastMessage = null;
+}
+
+TestLive.prototype =
+{
+ save: function(model, data)
+ {
+ this.lastMessage = {
+ op: 'SAVE',
+ key: model.$key(),
+ model: data
+ };
+
+ if ( this.onHandleMessage )
+ {
+ this.onHandleMessage( this.lastMessage );
+ }
+ },
+ remove: function(model)
+ {
+ this.lastMessage = {
+ op: 'REMOVE',
+ key: model.$key()
+ };
+
+ if ( this.onHandleMessage )
+ {
+ this.onHandleMessage( this.lastMessage );
+ }
+ },
+ liveSave: function(data)
+ {
+ var key = this.database.buildKeyFromInput( data );
+
+ this.database.liveSave( key, data );
+ },
+ liveRemove: function(input)
+ {
+ var key = this.database.buildKeyFromInput( input );
+
+ this.database.liveRemove( key );
+ }
+};
+
+// Rekord.rest."database name".(all|create|update|remove)
+
+Rekord.rest = function(database)
+{
+ var rest = Rekord.rest[ database.name ];
+
+ if ( !rest )
+ {
+ rest = Rekord.rest[ database.name ] = new TestRest();
+ }
+
+ return rest;
+};
+
+function TestRest()
+{
+ this.map = new Rekord.Map();
+ this.queries = new Rekord.Map();
+ this.status = 200;
+ this.returnValue = false;
+ this.delay = 0;
+ this.lastModel = this.lastRecord = this.lastOperation = null;
+}
+
+TestRest.prototype =
+{
+ finishDelayed: function(success, failure, returnValue)
+ {
+ var rest = this;
+
+ if ( rest.delay > 0 )
+ {
+ wait( rest.delay, function()
+ {
+ rest.finish( success, failure, returnValue );
+
+ });
+ }
+ else
+ {
+ rest.finish( success, failure, returnValue );
+ }
+ },
+ finish: function(success, failure, returnValue)
+ {
+ var offline = !Rekord.online || Rekord.forceOffline;
+ var status = offline ? 0 : this.status;
+ var successful = status >= 200 && status < 300;
+ var returnedValue = this.returnValue || returnValue;
+
+ if ( successful )
+ {
+ if ( success ) success( returnedValue, status );
+ }
+ else
+ {
+ if ( failure ) failure( returnedValue, status );
+ }
+ },
+ get: function(model, success, failure)
+ {
+ this.lastOperation = 'get';
+ this.lastModel = model;
+
+ var map = this.map;
+ function onGet()
+ {
+ var cached = map.get( model.$key() );
+ if ( cached ) {
+ success.call( this, cached );
+ } else {
+ failure.call( this, null, 404 );
+ }
+ }
+
+ this.finishDelayed( onGet, failure, null );
+ },
+ create: function(model, encoded, success, failure)
+ {
+ this.lastOperation = 'create';
+ this.lastModel = model;
+ this.lastRecord = encoded;
+
+ var map = this.map;
+ function onCreate()
+ {
+ map.put( model.$key(), encoded );
+ success.apply( this, arguments );
+ }
+
+ this.finishDelayed( onCreate, failure, {} );
+ },
+ update: function(model, encoded, success, failure)
+ {
+ this.lastOperation = 'update';
+ this.lastModel = model;
+ this.lastRecord = encoded;
+
+ var map = this.map;
+ function onUpdate()
+ {
+ var existing = map.get( model.$key() );
+ Rekord.transfer( encoded, existing );
+ success.apply( this, arguments );
+ }
+
+ this.finishDelayed( onUpdate, failure, {} );
+ },
+ remove: function(model, success, failure)
+ {
+ this.lastOperation = 'remove';
+ this.lastModel = model;
+
+ var map = this.map;
+ function onRemove()
+ {
+ map.remove( model.$key() );
+ success.apply( this, arguments );
+ }
+
+ this.finishDelayed( onRemove, failure, {} );
+ },
+ all: function(success, failure)
+ {
+ this.lastOperation = 'all';
+ this.finishDelayed( success, failure, this.map.values );
+ },
+ query: function(url, data, success, failure)
+ {
+ this.lastOperation = 'query';
+ this.lastRecord = data;
+ this.finishDelayed( success, failure, this.queries.get( url ) );
+ }
+};
diff --git a/test/cases/rekord-validation.js b/test/cases/rekord-validation.js
new file mode 100644
index 0000000..16f6df1
--- /dev/null
+++ b/test/cases/rekord-validation.js
@@ -0,0 +1,6765 @@
+module( 'Rekord Validation' );
+
+test( 'rule accepted custom message', function(assert)
+{
+ var prefix = 'rule_accepted_custom_message_';
+
+ var agreedMessage = 'You must agree to the terms and conditions!';
+
+ var Agreement = Rekord({
+ name: prefix + 'agreement',
+ fields: ['agreed', 'name'],
+ validation: {
+ rules: {
+ agreed: 'accepted'
+ },
+ messages: {
+ agreed: agreedMessage
+ }
+ }
+ });
+
+ var a = Agreement.create({name: 'Terms & Conditions', agreed: false});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {agreed: agreedMessage} );
+ deepEqual( a.$validationMessages, [agreedMessage] );
+
+ a.agreed = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule accepted default message', function(assert)
+{
+ var prefix = 'rule_accepted_default_message_';
+
+ var agreedMessage = 'agreed has not been accepted.';
+
+ var Agreement = Rekord({
+ name: prefix + 'agreement',
+ fields: ['agreed', 'name'],
+ validation: {
+ rules: {
+ agreed: 'accepted'
+ }
+ }
+ });
+
+ var a = Agreement.create({name: 'Terms & Conditions', agreed: false});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {agreed: agreedMessage} );
+ deepEqual( a.$validationMessages, [agreedMessage] );
+
+ a.agreed = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule accepted aliased message', function(assert)
+{
+ var prefix = 'rule_accepted_aliased_message_';
+
+ var agreedMessage = 'The Terms & Conditions has not been accepted.';
+
+ var Agreement = Rekord({
+ name: prefix + 'agreement',
+ fields: ['agreed', 'name'],
+ validation: {
+ rules: {
+ agreed: 'accepted'
+ },
+ aliases: {
+ agreed: 'The Terms & Conditions'
+ }
+ }
+ });
+
+ var a = Agreement.create({name: 'Terms & Conditions', agreed: false});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {agreed: agreedMessage} );
+ deepEqual( a.$validationMessages, [agreedMessage] );
+
+ a.agreed = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule accepted override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.accepted, {
+ message: 'You must accept {$alias}.'
+ });
+
+ var prefix = 'rule_accepted_override_message_';
+
+ var agreedMessage = 'You must accept the agreement.';
+
+ var Agreement = Rekord({
+ name: prefix + 'agreement',
+ fields: ['agreed', 'name'],
+ validation: {
+ rules: {
+ agreed: 'accepted'
+ },
+ aliases: {
+ agreed: 'the agreement'
+ }
+ }
+ });
+
+ var a = Agreement.create({name: 'Terms & Conditions', agreed: false});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {agreed: agreedMessage} );
+ deepEqual( a.$validationMessages, [agreedMessage] );
+
+ a.agreed = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+test( 'rule accepted add acceptable', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.accepted.acceptable, {
+ 'yup': true,
+ '1': false
+ });
+
+ var prefix = 'rule_accepted_add_acceptable_';
+
+ var agreedMessage = 'The agreement has not been accepted.';
+
+ var Agreement = Rekord({
+ name: prefix + 'agreement',
+ fields: ['agreed', 'name'],
+ validation: {
+ rules: {
+ agreed: 'accepted'
+ },
+ aliases: {
+ agreed: 'The agreement'
+ }
+ }
+ });
+
+ var a = Agreement.create({name: 'Terms & Conditions', agreed: 1});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {agreed: agreedMessage} );
+ deepEqual( a.$validationMessages, [agreedMessage] );
+
+ a.agreed = 'yup';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+// rule accepted invalid arguments
+
+test( 'rule after custom message', function(assert)
+{
+ var prefix = 'rule_after_custom_message_';
+
+ var expectedMessage = 'The due date must be after yesterday!';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'after:yesterday'
+ },
+ messages: {
+ due_date: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule after default message', function(assert)
+{
+ var prefix = 'rule_after_default_message_';
+
+ var expectedMessage = 'due_date must be after yesterday.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'after:yesterday'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule after aliased message', function(assert)
+{
+ var prefix = 'rule_after_alaised_message_';
+
+ var expectedMessage = 'The due date must be after yesterday.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'after:yesterday'
+ },
+ aliases: {
+ due_date: 'The due date'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule after override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.after, {
+ message: 'The {$alias} when given must be after {$date}.'
+ });
+
+ var prefix = 'rule_after_override_message_';
+
+ var expectedMessage = 'The due date when given must be after yesterday.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'after:yesterday'
+ },
+ aliases: {
+ due_date: 'due date'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+test( 'rule after raw params', function(assert)
+{
+ var prefix = 'rule_after_raw_params_';
+
+ var msInDay = 86400000;
+ var now = Date.now();
+ var yesterday = new Date( now - msInDay );
+ var expectedMessage = 'due_date must be after yesterday.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: {
+ after: yesterday
+ }
+ },
+ messages: {
+ due_date: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule after missing date', function(assert)
+{
+ var prefix = 'rule_after_missing_date_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'after'
+ }
+ }
+ });
+ }, 'after validation rule requires a date expression argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'after:'
+ }
+ }
+ });
+ }, 'after validation rule requires a date expression argument');
+});
+
+test( 'rule after invalid date', function(assert)
+{
+ var prefix = 'rule_after_invalid_date_';
+
+ throws(function() {
+ Rekord({
+ name: prefix,
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'after:NOTDATE'
+ }
+ }
+ });
+ }, 'NOTDATE is not a valid date expression for the after rule');
+});
+
+test( 'rule after_on custom message', function(assert)
+{
+ var prefix = 'rule_after_on_custom_message_';
+
+ var expectedMessage = 'The due date must be after or equal to today!';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'after_on:today'
+ },
+ messages: {
+ due_date: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule after_on default message', function(assert)
+{
+ var prefix = 'rule_after_on_default_message_';
+
+ var expectedMessage = 'due_date must be after or equal to today.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'after_on:today'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule after_on aliased message', function(assert)
+{
+ var prefix = 'rule_after_on_alaised_message_';
+
+ var expectedMessage = 'The due date must be after or equal to today.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'after_on:today'
+ },
+ aliases: {
+ due_date: 'The due date'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule after_on override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.after_on, {
+ message: 'The {$alias} when given must be on or after {$date}.'
+ });
+
+ var prefix = 'rule_after_on_override_message_';
+
+ var expectedMessage = 'The due date when given must be on or after today.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'after_on:today'
+ },
+ aliases: {
+ due_date: 'due date'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+test( 'rule after_on raw params', function(assert)
+{
+ var prefix = 'rule_after_on_raw_params_';
+
+ var msInDay = 86400000;
+ var now = Date.now();
+ var today = now;
+ var expectedMessage = 'due_date must be after or equal to today.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: {
+ after_on: today
+ }
+ },
+ messages: {
+ due_date: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now - msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule after_on missing date', function(assert)
+{
+ var prefix = 'rule_after_on_missing_date_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'after_on'
+ }
+ }
+ });
+ }, 'after_on validation rule requires a date expression argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'after_on:'
+ }
+ }
+ });
+ }, 'after_on validation rule requires a date expression argument');
+});
+
+test( 'rule after_on invalid date', function(assert)
+{
+ var prefix = 'rule_after_on_invalid_date_';
+
+ throws(function() {
+ Rekord({
+ name: prefix,
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'after_on:NOTDATE'
+ }
+ }
+ });
+ }, 'NOTDATE is not a valid date expression for the after_on rule');
+});
+
+test( 'rule before custom message', function(assert)
+{
+ var prefix = 'rule_before_custom_message_';
+
+ var expectedMessage = 'The due date must be before tomorrow!';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'before:tomorrow'
+ },
+ messages: {
+ due_date: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule before default message', function(assert)
+{
+ var prefix = 'rule_before_default_message_';
+
+ var expectedMessage = 'due_date must be before tomorrow.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'before:tomorrow'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule before aliased message', function(assert)
+{
+ var prefix = 'rule_before_alaised_message_';
+
+ var expectedMessage = 'The due date must be before tomorrow.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'before:tomorrow'
+ },
+ aliases: {
+ due_date: 'The due date'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule before override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.before, {
+ message: 'The {$alias} when given must be before {$date}.'
+ });
+
+ var prefix = 'rule_before_override_message_';
+
+ var expectedMessage = 'The due date when given must be before tomorrow.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'before:tomorrow'
+ },
+ aliases: {
+ due_date: 'due date'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+test( 'rule before raw params', function(assert)
+{
+ var prefix = 'rule_before_raw_params_';
+
+ var msInDay = 86400000;
+ var now = Date.now();
+ var tomorrow = now + msInDay - 1;
+ var expectedMessage = 'due_date must be before tomorrow.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: {
+ before: tomorrow
+ }
+ },
+ messages: {
+ due_date: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule before missing date', function(assert)
+{
+ var prefix = 'rule_before_missing_date_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'before'
+ }
+ }
+ });
+ }, 'before validation rule requires a date expression argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'before:'
+ }
+ }
+ });
+ }, 'before validation rule requires a date expression argument');
+});
+
+test( 'rule before invalid date', function(assert)
+{
+ var prefix = 'rule_before_invalid_date_';
+
+ throws(function() {
+ Rekord({
+ name: prefix,
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'before:NOTDATE'
+ }
+ }
+ });
+ }, 'NOTDATE is not a valid date expression for the before rule');
+});
+
+test( 'rule before_on custom message', function(assert)
+{
+ var prefix = 'rule_before_on_custom_message_';
+
+ var expectedMessage = 'The due date must be before or equal to tomorrow!';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'before_on:tomorrow'
+ },
+ messages: {
+ due_date: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule before_on default message', function(assert)
+{
+ var prefix = 'rule_before_on_default_message_';
+
+ var expectedMessage = 'due_date must be before or equal to tomorrow.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'before_on:tomorrow'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule before_on aliased message', function(assert)
+{
+ var prefix = 'rule_before_on_alaised_message_';
+
+ var expectedMessage = 'The due date must be before or equal to tomorrow.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'before_on:tomorrow'
+ },
+ aliases: {
+ due_date: 'The due date'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule before_on override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.before_on, {
+ message: 'The {$alias} when given must be before or equal to {$date}.'
+ });
+
+ var prefix = 'rule_before_on_override_message_';
+
+ var expectedMessage = 'The due date when given must be before or equal to tomorrow.';
+ var msInDay = 86400000;
+ var now = Date.now();
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: 'before_on:tomorrow'
+ },
+ aliases: {
+ due_date: 'due date'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+test( 'rule before_on raw params', function(assert)
+{
+ var prefix = 'rule_before_on_raw_params_';
+
+ var msInDay = 86400000;
+ var now = Date.now();
+ var tomorrow = now + msInDay;
+ var expectedMessage = 'due_date must be before or equal to tomorrow.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date'],
+ validation: {
+ rules: {
+ due_date: {
+ before_on: tomorrow
+ }
+ },
+ messages: {
+ due_date: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'a', due_date: now + msInDay * 2 });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = now;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule before_on missing date', function(assert)
+{
+ var prefix = 'rule_before_on_missing_date_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'before_on'
+ }
+ }
+ });
+ }, 'before_on validation rule requires a date expression argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'before_on:'
+ }
+ }
+ });
+ }, 'before_on validation rule requires a date expression argument');
+});
+
+test( 'rule before_on invalid date', function(assert)
+{
+ var prefix = 'rule_before_on_invalid_date_';
+
+ throws(function() {
+ Rekord({
+ name: prefix,
+ fields: ['due_date'],
+ validation: {
+ rules: {
+ due_date: 'before_on:NOTDATE'
+ }
+ }
+ });
+ }, 'NOTDATE is not a valid date expression for the before_on rule');
+});
+
+test( 'rule required custom message', function(assert)
+{
+ var prefix = 'rule_required_custom_message_';
+
+ var expectedMessage = 'Task name is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'required'
+ },
+ messages: {
+ name: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 'a';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule required default message', function(assert)
+{
+ var prefix = 'rule_required_default_message_';
+
+ var expectedMessage = 'name is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'required'
+ }
+ }
+ });
+
+ var a = Task.create({name: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 'a';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule required aliased message', function(assert)
+{
+ var prefix = 'rule_required_aliased_message_';
+
+ var expectedMessage = 'Task name is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'required'
+ },
+ aliases: {
+ name: 'Task name'
+ }
+ }
+ });
+
+ var a = Task.create({name: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 'a';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule required override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.required, {
+ message: '{$alias} is a required field.'
+ });
+
+ var prefix = 'rule_required_override_message_';
+
+ var expectedMessage = 'name is a required field.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'required'
+ }
+ }
+ });
+
+ var a = Task.create({name: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 'a';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+// rule required invalid arguments
+
+test( 'rule min string', function(assert)
+{
+ var prefix = 'rule_min_string_';
+
+ var expectedMessage = 'name must have a minimum of 3 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'min:3'
+ }
+ }
+ });
+
+ var a = Task.create({name: '22'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '333';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule min number', function(assert)
+{
+ var prefix = 'rule_min_number_';
+
+ var expectedMessage = 'count must be at least 3.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'min:3'
+ }
+ }
+ });
+
+ var a = Task.create({count: 2});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 3;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule min array', function(assert)
+{
+ var prefix = 'rule_min_array_';
+
+ var expectedMessage = 'options must have at least 3 items.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['options'],
+ validation: {
+ rules: {
+ options: 'min:3'
+ }
+ }
+ });
+
+ var a = Task.create({options: [1, 2]});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2, 3];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule min raw params', function(assert)
+{
+ var prefix = 'rule_min_raw_params_';
+
+ var expectedMessage = 'name must have a minimum of 3 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: {min: 3}
+ }
+ }
+ });
+
+ var a = Task.create({name: '22'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '333';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule min invalid arguments', function(assert)
+{
+ var prefix = 'rule_min_invalid_arguments_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'min'
+ }
+ }
+ });
+ }, 'min validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'min:'
+ }
+ }
+ });
+ }, 'min validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'min:X'
+ }
+ }
+ });
+ }, 'X is not a valid number for the min rule');
+});
+
+// rule min custom message
+// rule min aliased message
+// rule min override message
+
+test( 'rule greater_than string', function(assert)
+{
+ var prefix = 'rule_greater_than_string_';
+
+ var expectedMessage = 'name must have more than 3 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'greater_than:3'
+ }
+ }
+ });
+
+ var a = Task.create({name: '333'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '4444';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule greater_than number', function(assert)
+{
+ var prefix = 'rule_greater_than_number_';
+
+ var expectedMessage = 'count must be greater than 3.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'greater_than:3'
+ }
+ }
+ });
+
+ var a = Task.create({count: 3});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule greater_than array', function(assert)
+{
+ var prefix = 'rule_greater_than_array_';
+
+ var expectedMessage = 'options must have more than 3 items.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['options'],
+ validation: {
+ rules: {
+ options: 'greater_than:3'
+ }
+ }
+ });
+
+ var a = Task.create({options: [1, 2, 3]});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2, 3, 4];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule greater_than invalid arguments', function(assert)
+{
+ var prefix = 'rule_greater_than_invalid_arguments_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'greater_than'
+ }
+ }
+ });
+ }, 'greater_than validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'greater_than:'
+ }
+ }
+ });
+ }, 'greater_than validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'greater_than:X'
+ }
+ }
+ });
+ }, 'X is not a valid number for the greater_than rule');
+});
+
+// rule greater_than custom message
+// rule greater_than aliased message
+// rule greater_than override message
+// rule greater_than raw params
+
+test( 'rule max string', function(assert)
+{
+ var prefix = 'rule_max_string_';
+
+ var expectedMessage = 'name must have no more than 3 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'max:3'
+ }
+ }
+ });
+
+ var a = Task.create({name: '4444'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '333';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule max number', function(assert)
+{
+ var prefix = 'rule_max_number_';
+
+ var expectedMessage = 'count must be no more than 3.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'max:3'
+ }
+ }
+ });
+
+ var a = Task.create({count: 4});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 3;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule max array', function(assert)
+{
+ var prefix = 'rule_max_array_';
+
+ var expectedMessage = 'options must have no more than 3 items.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['options'],
+ validation: {
+ rules: {
+ options: 'max:3'
+ }
+ }
+ });
+
+ var a = Task.create({options: [1, 2, 3, 4]});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2, 3];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule max invalid arguments', function(assert)
+{
+ var prefix = 'rule_max_invalid_arguments_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'max'
+ }
+ }
+ });
+ }, 'max validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'max:'
+ }
+ }
+ });
+ }, 'max validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'max:X'
+ }
+ }
+ });
+ }, 'X is not a valid number for the max rule');
+});
+
+// rule max custom message
+// rule max aliased message
+// rule max override message
+// rule max raw params
+
+test( 'rule less_than string', function(assert)
+{
+ var prefix = 'rule_less_than_string_';
+
+ var expectedMessage = 'name must have less than 3 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'less_than:3'
+ }
+ }
+ });
+
+ var a = Task.create({name: '333'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '22';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule less_than number', function(assert)
+{
+ var prefix = 'rule_less_than_number_';
+
+ var expectedMessage = 'count must be less than 3.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'less_than:3'
+ }
+ }
+ });
+
+ var a = Task.create({count: 3});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 2;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule less_than array', function(assert)
+{
+ var prefix = 'rule_less_than_array_';
+
+ var expectedMessage = 'options must have less than 3 items.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['options'],
+ validation: {
+ rules: {
+ options: 'less_than:3'
+ }
+ }
+ });
+
+ var a = Task.create({options: [1, 2, 3]});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule less_than invalid arguments', function(assert)
+{
+ var prefix = 'rule_less_than_invalid_arguments_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'less_than'
+ }
+ }
+ });
+ }, 'less_than validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'less_than:'
+ }
+ }
+ });
+ }, 'less_than validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'less_than:X'
+ }
+ }
+ });
+ }, 'X is not a valid number for the less_than rule');
+});
+
+// rule less_than custom message
+// rule less_than aliased message
+// rule less_than override message
+// rule less_than raw params
+
+test( 'rule equal string', function(assert)
+{
+ var prefix = 'rule_equal_string_';
+
+ var expectedMessage = 'name must have 3 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'equal:3'
+ }
+ }
+ });
+
+ var a = Task.create({name: '22'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '333';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule equal number', function(assert)
+{
+ var prefix = 'rule_equal_number_';
+
+ var expectedMessage = 'count must equal 3.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'equal:3'
+ }
+ }
+ });
+
+ var a = Task.create({count: 4});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 3;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule equal array', function(assert)
+{
+ var prefix = 'rule_equal_array_';
+
+ var expectedMessage = 'options must have 3 items.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['options'],
+ validation: {
+ rules: {
+ options: 'equal:3'
+ }
+ }
+ });
+
+ var a = Task.create({options: [1, 2, 3, 4]});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2, 3];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule equal invalid arguments', function(assert)
+{
+ var prefix = 'rule_equal_invalid_arguments_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'equal'
+ }
+ }
+ });
+ }, 'equal validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'equal:'
+ }
+ }
+ });
+ }, 'equal validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'equal:X'
+ }
+ }
+ });
+ }, 'X is not a valid number for the equal rule');
+});
+
+// rule equal custom message
+// rule equal aliased message
+// rule equal override message
+// rule equal raw params
+
+test( 'rule not_equal string', function(assert)
+{
+ var prefix = 'rule_not_equal_string_';
+
+ var expectedMessage = 'name must not have 3 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'not_equal:3'
+ }
+ }
+ });
+
+ var a = Task.create({name: '333'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '22';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_equal number', function(assert)
+{
+ var prefix = 'rule_not_equal_number_';
+
+ var expectedMessage = 'count must not equal 3.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_equal:3'
+ }
+ }
+ });
+
+ var a = Task.create({count: 3});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_equal array', function(assert)
+{
+ var prefix = 'rule_not_equal_array_';
+
+ var expectedMessage = 'options must not have 3 items.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['options'],
+ validation: {
+ rules: {
+ options: 'not_equal:3'
+ }
+ }
+ });
+
+ var a = Task.create({options: [1, 2, 3]});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_equal invalid arguments', function(assert)
+{
+ var prefix = 'rule_not_equal_invalid_arguments_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_equal'
+ }
+ }
+ });
+ }, 'not_equal validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_equal:'
+ }
+ }
+ });
+ }, 'not_equal validation rule requires a number argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_equal:X'
+ }
+ }
+ });
+ }, 'X is not a valid number for the not_equal rule');
+});
+
+// rule not_equal custom message
+// rule not_equal aliased message
+// rule not_equal override message
+// rule not_equal raw params
+
+test( 'rule array custom message', function(assert)
+{
+ var prefix = 'rule_array_custom_message_';
+
+ var expectedMessage = 'Task name must be an array.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'array'
+ },
+ messages: {
+ name: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = [];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule array default message', function(assert)
+{
+ var prefix = 'rule_array_default_message_';
+
+ var expectedMessage = 'name must be an array.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'array'
+ }
+ }
+ });
+
+ var a = Task.create({name: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = [];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule array aliased message', function(assert)
+{
+ var prefix = 'rule_array_aliased_message_';
+
+ var expectedMessage = 'Task name must be an array.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'array'
+ },
+ aliases: {
+ name: 'Task name'
+ }
+ }
+ });
+
+ var a = Task.create({name: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = [];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule array override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.array, {
+ message: '{$alias} is an array field.'
+ });
+
+ var prefix = 'rule_array_override_message_';
+
+ var expectedMessage = 'name is an array field.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'array'
+ }
+ }
+ });
+
+ var a = Task.create({name: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = [];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+// rule array invalid arguments
+
+test( 'rule string default message', function(assert)
+{
+ var prefix = 'rule_string_default_message_';
+
+ var expectedMessage = 'name must be a string.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'string'
+ }
+ }
+ });
+
+ var a = Task.create({name: 4});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 'what';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule string custom message
+// rule string aliased message
+// rule string override message
+// rule string invalid arguments
+
+test( 'rule number default message', function(assert)
+{
+ var prefix = 'rule_number_default_message_';
+
+ var expectedMessage = 'name must be a number.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'number'
+ }
+ }
+ });
+
+ var a = Task.create({name: '4'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule number custom message
+// rule number aliased message
+// rule number override message
+// rule number invalid arguments
+
+test( 'rule object default message', function(assert)
+{
+ var prefix = 'rule_object_default_message_';
+
+ var expectedMessage = 'name must be an object.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'object'
+ }
+ }
+ });
+
+ var a = Task.create({name: '4'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = {butt: ['what']};
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule object custom message
+// rule object aliased message
+// rule object override message
+// rule object invalid arguments
+
+test( 'rule boolean default message', function(assert)
+{
+ var prefix = 'rule_boolean_default_message_';
+
+ var expectedMessage = 'name must be a true or false.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'boolean'
+ }
+ }
+ });
+
+ var a = Task.create({name: '4'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule boolean custom message
+// rule boolean aliased message
+// rule boolean override message
+// rule boolean invalid arguments
+
+test( 'rule model default message', function(assert)
+{
+ var prefix = 'rule_model_default_message_';
+
+ var expectedMessage = 'name must have a value.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'model'
+ }
+ }
+ });
+
+ var b = Task.create();
+ var a = Task.create({name: null});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = b;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule model custom message
+// rule model aliased message
+// rule model override message
+// rule model invalid arguments
+
+test( 'rule between string', function(assert)
+{
+ var prefix = 'rule_between_string_';
+
+ var expectedMessage = 'name must have between 3 to 8 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'between:3,8'
+ }
+ }
+ });
+
+ var a = Task.create({name: '22'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '999999999';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '333';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.name = '88888888';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule between number', function(assert)
+{
+ var prefix = 'rule_between_number_';
+
+ var expectedMessage = 'count must be between 3 and 8.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'between:3,8'
+ }
+ }
+ });
+
+ var a = Task.create({count: 2});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 9;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 3;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.count = 8;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule between array', function(assert)
+{
+ var prefix = 'rule_between_array_';
+
+ var expectedMessage = 'options must have between 3 to 8 items.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['options'],
+ validation: {
+ rules: {
+ options: 'between:3,8'
+ }
+ }
+ });
+
+ var a = Task.create({options: [1, 2]});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2, 3];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.options = [1, 2, 3, 4, 5, 6, 7, 8];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule between raw params', function(assert)
+{
+ var prefix = 'rule_between_raw_params_';
+
+ var expectedMessage = 'count must be between 3 and 8.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: {
+ between: [3, 8]
+ }
+ }
+ }
+ });
+
+ var a = Task.create({count: 2});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 9;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 3;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.count = 8;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule between raw params object', function(assert)
+{
+ var prefix = 'rule_between_raw_params_object_';
+
+ var expectedMessage = 'count must be between 3 and 8.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: {
+ between: {start: 3, end: 8}
+ }
+ }
+ }
+ });
+
+ var a = Task.create({count: 2});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 9;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 3;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.count = 8;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule between invalid arguments', function(assert)
+{
+ var prefix = 'rule_between_invalid_arguments_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'between'
+ }
+ }
+ });
+ }, 'between validation rule requires a range argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'between:'
+ }
+ }
+ });
+ }, 'between validation rule requires a range argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan,',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'between:X'
+ }
+ }
+ });
+ }, 'X is not a valid range of numbers for the between rule');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan,num',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'between:X,3'
+ }
+ }
+ });
+ }, 'X,3 is not a valid range of numbers for the between rule');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'num,nan',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'between:4,'
+ }
+ }
+ });
+ }, '4, is not a valid range of numbers for the between rule');
+});
+
+// rule between custom message
+// rule between aliased message
+// rule between override message
+
+test( 'rule not_between string', function(assert)
+{
+ var prefix = 'rule_not_between_string_';
+
+ var expectedMessage = 'name must not have between 3 to 8 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'not_between:3,8'
+ }
+ }
+ });
+
+ var a = Task.create({name: '333'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '88888888';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '22';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.name = '999999999';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_between number', function(assert)
+{
+ var prefix = 'rule_not_between_number_';
+
+ var expectedMessage = 'count must not be between 3 and 8.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_between:3,8'
+ }
+ }
+ });
+
+ var a = Task.create({count: 3});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 8;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {count: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.count = 2;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.count = 9;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_between array', function(assert)
+{
+ var prefix = 'rule_not_between_array_';
+
+ var expectedMessage = 'options must not have between 3 to 8 items.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['options'],
+ validation: {
+ rules: {
+ options: 'not_between:3,8'
+ }
+ }
+ });
+
+ var a = Task.create({options: [1, 2, 3]});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2, 3, 4, 5, 6, 7, 8];
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {options: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.options = [1, 2];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.options = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_between raw params', function(assert)
+{
+ var prefix = 'rule_not_between_raw_params_';
+
+ var expectedMessage = 'name must not have between 3 to 8 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: {
+ not_between: [3, 8]
+ }
+ }
+ }
+ });
+
+ var a = Task.create({name: '333'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '88888888';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '22';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.name = '999999999';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_between raw params object', function(assert)
+{
+ var prefix = 'rule_not_between_raw_params_object_';
+
+ var expectedMessage = 'name must not have between 3 to 8 characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: {
+ not_between: {start: 3, end: 8}
+ }
+ }
+ }
+ });
+
+ var a = Task.create({name: '333'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '88888888';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '22';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.name = '999999999';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_between invalid arguments', function(assert)
+{
+ var prefix = 'rule_not_between_invalid_arguments_';
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'no_colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_between'
+ }
+ }
+ });
+ }, 'not_between validation rule requires a range argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'colon',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_between:'
+ }
+ }
+ });
+ }, 'not_between validation rule requires a range argument');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan,',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_between:X'
+ }
+ }
+ });
+ }, 'X is not a valid range of numbers for the not_between rule');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'nan,num',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_between:X,3'
+ }
+ }
+ });
+ }, 'X,3 is not a valid range of numbers for the not_between rule');
+
+ throws(function() {
+ Rekord({
+ name: prefix + 'num,nan',
+ fields: ['count'],
+ validation: {
+ rules: {
+ count: 'not_between:4,'
+ }
+ }
+ });
+ }, '4, is not a valid range of numbers for the not_between rule');
+});
+
+// rule not_between custom message
+// rule not_between aliased message
+// rule not_between override message
+
+test( 'rule whole custom message', function(assert)
+{
+ var prefix = 'rule_whole_custom_message_';
+
+ var expectedMessage = 'Task name must be a whole number.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'whole'
+ },
+ messages: {
+ name: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 4.1});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '4';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule whole default message', function(assert)
+{
+ var prefix = 'rule_whole_default_message_';
+
+ var expectedMessage = 'name must be a whole number.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'whole'
+ }
+ }
+ });
+
+ var a = Task.create({name: 2.3});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '2';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule whole aliased message', function(assert)
+{
+ var prefix = 'rule_whole_aliased_message_';
+
+ var expectedMessage = 'Task name must be a whole number.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'whole'
+ },
+ aliases: {
+ name: 'Task name'
+ }
+ }
+ });
+
+ var a = Task.create({name: 4.5});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '4';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule whole override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.whole, {
+ message: '{$alias} must be an integer.'
+ });
+
+ var prefix = 'rule_whole_override_message_';
+
+ var expectedMessage = 'name must be an integer.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'whole'
+ }
+ }
+ });
+
+ var a = Task.create({name: 6.6});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '6';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+// rule whole invalid arguments
+
+test( 'rule numeric custom message', function(assert)
+{
+ var prefix = 'rule_numeric_custom_message_';
+
+ var expectedMessage = 'Task name must be numeric.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'numeric'
+ },
+ messages: {
+ name: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({name: 'taco tuesday'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 23.4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule numeric default message', function(assert)
+{
+ var prefix = 'rule_numeric_default_message_';
+
+ var expectedMessage = 'name must be numeric.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'numeric'
+ }
+ }
+ });
+
+ var a = Task.create({name: null});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '2.5';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule numeric aliased message', function(assert)
+{
+ var prefix = 'rule_numeric_aliased_message_';
+
+ var expectedMessage = 'Task name must be numeric.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'numeric'
+ },
+ aliases: {
+ name: 'Task name'
+ }
+ }
+ });
+
+ var a = Task.create({name: 'y4.5'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 4.1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule numeric override message', function(assert)
+{
+ var pop = pushChanges( Rekord.Validation.Rules.numeric, {
+ message: '{$alias} must be a decimal.'
+ });
+
+ var prefix = 'rule_numeric_override_message_';
+
+ var expectedMessage = 'name must be a decimal.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'numeric'
+ }
+ }
+ });
+
+ var a = Task.create({name: true});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = '6.6';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ pop();
+});
+
+// rule numeric invalid arguments
+
+test( 'rule alpha default message', function(assert)
+{
+ var prefix = 'rule_alpha_default_message_';
+
+ var expectedMessage = 'text should only contain alphabetic characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: 'alpha'
+ }
+ }
+ });
+
+ var a = Task.create({text: 'ayo2'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'w hat';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'okay';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = 'sure';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule alpha custom message
+// rule alpha aliased message
+// rule alpha override message
+// rule alpha invalid arguments
+
+test( 'rule alpha_dash default message', function(assert)
+{
+ var prefix = 'rule_alpha_dash_default_message_';
+
+ var expectedMessage = 'text should only contain alpha-numeric characters, dashes, and underscores.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: 'alpha_dash'
+ }
+ }
+ });
+
+ var a = Task.create({text: 'ayo2 '});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'what?';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'o_kay34';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = 's-ure';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule alpha_dash custom message
+// rule alpha_dash aliased message
+// rule alpha_dash override message
+// rule alpha_dash invalid arguments
+
+test( 'rule alpha_num default message', function(assert)
+{
+ var prefix = 'rule_alpha_num_default_message_';
+
+ var expectedMessage = 'text should only contain alpha-numeric characters.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: 'alpha_num'
+ }
+ }
+ });
+
+ var a = Task.create({text: 'ayo2 '});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'what_';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'okay34';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = 'sure';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule alpha_num custom message
+// rule alpha_num aliased message
+// rule alpha_num override message
+// rule alpha_num invalid arguments
+
+test( 'rule email default message', function(assert)
+{
+ var prefix = 'rule_email_default_message_';
+
+ var expectedMessage = 'text is not a valid email.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: 'email'
+ }
+ }
+ });
+
+ var a = Task.create({text: '@meow.com'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'what@taco';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'pdiffenderfer@gmail.com';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = 'rekord.js+extra@gmail-site.com';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule email custom message
+// rule email aliased message
+// rule email override message
+// rule email invalid arguments
+
+test( 'rule url default message', function(assert)
+{
+ var prefix = 'rule_url_default_message_';
+
+ var expectedMessage = 'text is not a valid URL.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: 'url'
+ }
+ }
+ });
+
+ var a = Task.create({text: '@meow'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'ftp://website.com';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'http://www.google.com/maps?q=Your+Mom';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = 'google.com';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule url custom message
+// rule url aliased message
+// rule url override message
+// rule url invalid arguments
+
+test( 'rule uri default message', function(assert)
+{
+ var prefix = 'rule_uri_default_message_';
+
+ var expectedMessage = 'text is not a valid URI.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: 'uri'
+ }
+ }
+ });
+
+ var a = Task.create({text: '@meow'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = '192.168.2.1';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'http://www.google.com/maps?q=Your+Mom';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = 'ftp://website.domain';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule uri custom message
+// rule uri aliased message
+// rule uri override message
+// rule uri invalid arguments
+
+test( 'rule phone default message', function(assert)
+{
+ var prefix = 'rule_phone_default_message_';
+
+ var expectedMessage = 'text is not a valid phone number.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: 'phone'
+ }
+ }
+ });
+
+ var a = Task.create({text: '666666'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = '1(717)341-567';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = '1(717)341-5675';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = '717 3415678';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule phone custom message
+// rule phone aliased message
+// rule phone override message
+// rule phone invalid arguments
+
+test( 'rule regex default message', function(assert)
+{
+ var prefix = 'rule_regex_default_message_';
+
+ var expectedMessage = 'text is not a valid value.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: 'regex:/^[a-z][0-9][_-]$/i'
+ }
+ }
+ });
+
+ var a = Task.create({text: 'z0'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'r1.';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'Q4_';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = 't3-';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule regex raw params', function(assert)
+{
+ var prefix = 'rule_regex_raw_params_';
+
+ var expectedMessage = 'text is not a valid value.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['text'],
+ validation: {
+ rules: {
+ text: {
+ regex: /^[a-z][0-9][_-]$/i
+ }
+ }
+ }
+ });
+
+ var a = Task.create({text: 'z0'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'r1.';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {text: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.text = 'Q4_';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.text = 't3-';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule regex custom message
+// rule regex aliased message
+// rule regex override message
+// rule regex invalid arguments
+
+test( 'rule date_like default message', function(assert)
+{
+ var prefix = 'rule_date_like_default_message_';
+
+ var expectedMessage = 'value must be a valid date.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'date_like'
+ }
+ }
+ });
+
+ var a = Task.create({value: '01/2016'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'not date either';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 67867567; // ms
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = '01/03/1989';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = new Date();
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule date_like custom message
+// rule date_like aliased message
+// rule date_like override message
+// rule date_like invalid arguments
+
+test( 'rule confirmed default message', function(assert)
+{
+ var prefix = 'rule_confirmed_default_message_';
+
+ var expectedMessage = 'value must match value_again.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value', 'value_again'],
+ validation: {
+ rules: {
+ value: 'confirmed:value_again'
+ }
+ }
+ });
+
+ var a = Task.create({value: 'taco', value_again: 'not taco'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'not taco either';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'not taco'; // ms
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 42;
+ a.value_again = 42;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule confirmed multiple fields
+// rule confirmed custom message
+// rule confirmed aliased message
+// rule confirmed override message
+// rule confirmed invalid arguments
+
+test( 'rule different default message', function(assert)
+{
+ var prefix = 'rule_different_default_message_';
+
+ var expectedMessage = 'value must not match value_again.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value', 'value_again'],
+ validation: {
+ rules: {
+ value: 'different:value_again'
+ }
+ }
+ });
+
+ var a = Task.create({value: 'taco', value_again: 'taco'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 1;
+ a.value_again = 1;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 1;
+ a.value_again = '1';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 42;
+ a.value_again = 43;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule different multiple fields
+// rule different custom message
+// rule different aliased message
+// rule different override message
+// rule different invalid arguments
+
+test( 'rule if_valid default message', function(assert)
+{
+ var prefix = 'rule_if_valid_default_message_';
+
+ var expectedMessage1 = 'name is required.';
+ var expectedMessage2 = 'done must be a yes or no.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'done'],
+ validation: {
+ rules: {
+ name: 'required',
+ done: 'if_valid:name|yesno'
+ }
+ }
+ });
+
+ var a = Task.create({name: '', done: 2});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage1} );
+ deepEqual( a.$validationMessages, [expectedMessage1] );
+
+ a.name = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage1} );
+ deepEqual( a.$validationMessages, [expectedMessage1] );
+
+ a.name = 'taco';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {done: expectedMessage2} );
+ deepEqual( a.$validationMessages, [expectedMessage2] );
+
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule if_valid multiple fields
+// rule if_valid custom message
+// rule if_valid aliased message
+// rule if_valid override message
+// rule if_valid invalid arguments
+
+test( 'rule exists custom message', function(assert)
+{
+ var prefix = 'rule_exists_custom_message_';
+
+ var expectedMessage = 'Value must match an existing value.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'exists'
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ Task.create({value: 'exists'});
+ Task.create({value: 'also exists'});
+
+ var a = Task.create({value: 'not exists'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'does not exist either';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'exists';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 'also exists';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule exists raw params', function(assert)
+{
+ var prefix = 'rule_exists_raw_params';
+
+ var expectedMessage = 'Value must match an existing value.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: {
+ exists: {field: 'value'}
+ }
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ Task.create({value: 'exists'});
+ Task.create({value: 'also exists'});
+
+ var a = Task.create({value: 'not exists'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'does not exist either';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'exists';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 'also exists';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule exists foreign class
+// rule exists different field
+// rule exists foreign class field
+// rule exists multiple fields
+// rule exists default message
+// rule exists aliased message
+// rule exists override message
+// rule exists raw params model class
+// rule exists raw params models array
+// rule exists invalid arguments
+
+test( 'rule unique custom message', function(assert)
+{
+ var prefix = 'rule_unique_custom_message_';
+
+ var expectedMessage = 'Value must be unique.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'unique'
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ Task.create({value: 'unique1'});
+ Task.create({value: 'unique2'});
+
+ var a = Task.create({value: 'unique1'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'unique2';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'unique3';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = '';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule unique raw params', function(assert)
+{
+ var prefix = 'rule_unique_raw_params_';
+
+ var expectedMessage = 'Value must be unique.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: {
+ unique: {field: 'value'}
+ }
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ Task.create({value: 'unique1'});
+ Task.create({value: 'unique2'});
+
+ var a = Task.create({value: 'unique1'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'unique2';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'unique3';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = '';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule unique foreign class
+// rule unique different field
+// rule unique foreign class field
+// rule unique multiple fields
+// rule unique default message
+// rule unique aliased message
+// rule unique override message
+// rule unique raw params model class
+// rule unique raw params models array
+// rule unique invalid arguments
+
+test( 'rule contains default message', function(assert)
+{
+ var prefix = 'rule_contains_default_message_';
+
+ var expectedMessage = 'tasks does not contain an item whose done equals true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'done', 'list_id']
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: 'contains:done,true'
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var t0 = Task.create({name: 't0', done: false});
+ var t1 = Task.create({name: 't1'});
+ var a = TaskList.create({
+ name: 'list1',
+ tasks: [ t0, t1 ]
+ });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t0.done = 1;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ t0.done = true;
+ t1.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule contains raw params', function(assert)
+{
+ var prefix = 'rule_contains_raw_params_';
+
+ var expectedMessage = 'tasks does not contain an item whose done equals true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'done', 'list_id']
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: {
+ contains: {
+ field: 'done',
+ value: true,
+ equals: Rekord.equals
+ }
+ }
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var t0 = Task.create({name: 't0', done: false});
+ var t1 = Task.create({name: 't1'});
+ var a = TaskList.create({
+ name: 'list1',
+ tasks: [ t0, t1 ]
+ });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t0.done = 1;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ t0.done = true;
+ t1.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule contains raw params array', function(assert)
+{
+ var prefix = 'rule_contains_raw_params_array_';
+
+ var expectedMessage = 'tasks does not contain an item whose done equals true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'done', 'list_id']
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: {
+ contains: ['done', true, Rekord.equals]
+ }
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var t0 = Task.create({name: 't0', done: false});
+ var t1 = Task.create({name: 't1'});
+ var a = TaskList.create({
+ name: 'list1',
+ tasks: [ t0, t1 ]
+ });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t0.done = 1;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ t0.done = true;
+ t1.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule contains custom message
+// rule contains aliased message
+// rule contains override message
+// rule contains invalid arguments
+
+test( 'rule not_contains default message', function(assert)
+{
+ var prefix = 'rule_not_contains_default_message_';
+
+ var expectedMessage = 'tasks contains an item whose done equals true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'done', 'list_id']
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: 'not_contains:done,true'
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var t0 = Task.create({name: 't0', done: true});
+ var t1 = Task.create({name: 't1'});
+ var a = TaskList.create({
+ name: 'list1',
+ tasks: [ t0, t1 ]
+ });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t0.done = false;
+ t1.done = true;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ t0.done = null;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_contains raw params', function(assert)
+{
+ var prefix = 'rule_not_contains_raw_params_';
+
+ var expectedMessage = 'tasks contains an item whose done equals true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'done', 'list_id']
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: {
+ not_contains: {
+ field: 'done',
+ value: true,
+ equals: Rekord.equals
+ }
+ }
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var t0 = Task.create({name: 't0', done: true});
+ var t1 = Task.create({name: 't1'});
+ var a = TaskList.create({
+ name: 'list1',
+ tasks: [ t0, t1 ]
+ });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t0.done = false;
+ t1.done = true;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ t0.done = null;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_contains raw params array', function(assert)
+{
+ var prefix = 'rule_not_contains_raw_params_array_';
+
+ var expectedMessage = 'tasks contains an item whose done equals true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'done', 'list_id']
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: {
+ not_contains: ['done', true, Rekord.equals]
+ }
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var t0 = Task.create({name: 't0', done: true});
+ var t1 = Task.create({name: 't1'});
+ var a = TaskList.create({
+ name: 'list1',
+ tasks: [ t0, t1 ]
+ });
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t0.done = false;
+ t1.done = true;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ t0.done = null;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule not_contains custom message
+// rule not_contains aliased message
+// rule not_contains override message
+// rule not_contains invalid arguments
+
+test( 'rule in default message', function(assert)
+{
+ var prefix = 'rule_in_default_message_';
+
+ var expectedMessage = 'value must be one of A, B, C, or true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'in:A,B,C,true'
+ }
+ }
+ });
+
+ var a = Task.create({value: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'D';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'A';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 'B';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule in raw params', function(assert)
+{
+ var prefix = 'rule_in_raw_params_';
+
+ var expectedMessage = 'value must be one of A, B, C, or true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: {
+ in: ['A', 'B', 'C', true]
+ }
+ }
+ }
+ });
+
+ var a = Task.create({value: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'D';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'A';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 'B';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule in custom message
+// rule in aliased message
+// rule in override message
+// rule in invalid arguments
+// rule in raw params function
+
+test( 'rule not_in default message', function(assert)
+{
+ var prefix = 'rule_not_in_default_message_';
+
+ var expectedMessage = 'value must not be one of A, B, C, or true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'not_in:A,B,C,true'
+ }
+ }
+ });
+
+ var a = Task.create({value: 'A'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = true;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'D';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = null;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule not_in raw params', function(assert)
+{
+ var prefix = 'rule_not_in_raw_params_';
+
+ var expectedMessage = 'value must not be one of A, B, C, or true.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: {
+ not_in: ['A', 'B', 'C', true]
+ }
+ }
+ }
+ });
+
+ var a = Task.create({value: 'A'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = true;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'D';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = null;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule not_in custom message
+// rule not_in aliased message
+// rule not_in override message
+// rule not_in invalid arguments
+// rule not_in raw params function
+
+test( 'rule yesno default message', function(assert)
+{
+ var prefix = 'rule_yesno_default_message_';
+
+ var expectedMessage = 'value must be a yes or no.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'yesno'
+ }
+ }
+ });
+
+ var a = Task.create({value: 'A'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 'f';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule yesno custom yesno
+// rule yesno custom message
+// rule yesno aliased message
+// rule yesno override message
+// rule yesno invalid arguments
+
+test( 'rule required_if default message', function(assert)
+{
+ var prefix = 'rule_required_if_default_message_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done'],
+ validation: {
+ rules: {
+ due_date: 'required_if:done,true,1'
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: true});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = '';
+ a.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule required_if raw params', function(assert)
+{
+ var prefix = 'rule_required_if_raw_params_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done'],
+ validation: {
+ rules: {
+ due_date: {
+ required_if: {
+ field: 'done',
+ values: [true, 1]
+ }
+ }
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: true});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = '';
+ a.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule required_if raw params array', function(assert)
+{
+ var prefix = 'rule_required_if_raw_params_array_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done'],
+ validation: {
+ rules: {
+ due_date: {
+ required_if: ['done', true, 1]
+ }
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: true});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = '';
+ a.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule required_if custom message
+// rule required_if aliased message
+// rule required_if override message
+// rule required_if invalid arguments
+
+test( 'rule required_unless default message', function(assert)
+{
+ var prefix = 'rule_required_unless_default_message_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done'],
+ validation: {
+ rules: {
+ due_date: 'required_unless:done,false,0'
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: true});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = '';
+ a.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule required_unless raw params', function(assert)
+{
+ var prefix = 'rule_required_unless_raw_params_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done'],
+ validation: {
+ rules: {
+ due_date: {
+ required_unless: {
+ field: 'done',
+ values: [false, 0]
+ }
+ }
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: true});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = '';
+ a.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule required_unless raw params array', function(assert)
+{
+ var prefix = 'rule_required_unless_raw_params_array_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done'],
+ validation: {
+ rules: {
+ due_date: {
+ required_unless: ['done', false, 0]
+ }
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: true});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = '';
+ a.done = false;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule required_unless custom message
+// rule required_unless aliased message
+// rule required_unless override message
+// rule required_unless invalid arguments
+
+test( 'rule required_with default message', function(assert)
+{
+ var prefix = 'rule_required_with_default_message_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done', 'summary'],
+ validation: {
+ rules: {
+ due_date: 'required_with:done,summary'
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: true, summary: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.done = null;
+ a.summary = 'summary';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = '';
+ a.done = null;
+ a.summary = '';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule required_with custom message
+// rule required_with aliased message
+// rule required_with override message
+// rule required_with invalid arguments
+
+test( 'rule required_with_all default message', function(assert)
+{
+ var prefix = 'rule_required_with_all_default_message_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done', 'summary'],
+ validation: {
+ rules: {
+ due_date: 'required_with_all:done,summary'
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: true, summary: 'summary'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.done = 'y';
+ a.summary = 'summary';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = null;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = '';
+ a.done = null;
+ a.summary = '';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule required_with_all custom message
+// rule required_with_all aliased message
+// rule required_with_all override message
+// rule required_with_all invalid arguments
+
+test( 'rule required_without default message', function(assert)
+{
+ var prefix = 'rule_required_without_default_message_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done', 'summary'],
+ validation: {
+ rules: {
+ due_date: 'required_without:done,summary'
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: null, summary: 'summary'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.done = 'y';
+ a.summary = '';
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = null;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = 'taco';
+ a.done = null;
+ a.summary = '';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule required_without custom message
+// rule required_without aliased message
+// rule required_without override message
+// rule required_without invalid arguments
+
+test( 'rule required_without_all default message', function(assert)
+{
+ var prefix = 'rule_required_without_all_default_message_';
+
+ var expectedMessage = 'due_date is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['due_date', 'done', 'summary'],
+ validation: {
+ rules: {
+ due_date: 'required_without_all:done,summary'
+ }
+ }
+ });
+
+ var a = Task.create({due_date: '', done: null, summary: ''});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.summary = null;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {due_date: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.due_date = '01/01/2000';
+ a.summary = '';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.due_date = null;
+ a.done = true;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.done = null;
+ a.summary = 'summary';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule required_without_all custom message
+// rule required_without_all aliased message
+// rule required_without_all override message
+// rule required_without_all invalid arguments
+
+test( 'rule if default message', function(assert)
+{
+ var prefix = 'rule_if_default_message_';
+
+ var expectedMessage = 'value must be odd when over 6.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'if:value:min:6|regex:/[13579]$/' // odd
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 8});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 10;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 7;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 21;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule if raw params', function(assert)
+{
+ var prefix = 'rule_if_raw_params_';
+
+ var expectedMessage = 'value must be odd when over 6.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: {
+ if: {
+ field: 'value',
+ rules: {
+ min: 6
+ }
+ },
+ regex: /[13579]$/ // odd
+ }
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 8});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 10;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 7;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 21;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule if all', function(assert)
+{
+ var prefix = 'rule_if_all_';
+
+ var expectedMessage = 'value must be odd when over 6 and under 12.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'if:value:min:6\\|max:12|regex:/[13579]$/' // odd
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 8});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 10;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 11;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 7;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 14;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule if all raw params', function(assert)
+{
+ var prefix = 'rule_if_all_raw_params_';
+
+ var expectedMessage = 'value must be odd when over 6 and under 12.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: {
+ if: {
+ rules: {
+ min: 6,
+ max: 12
+ }
+ },
+ regex: /[13579]$/ // odd
+ }
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 8});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 10;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 11;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 7;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 14;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule if custom message
+// rule if aliased message
+// rule if override message
+// rule if invalid arguments
+// rule if raw params array
+// rule if default field
+
+test( 'rule if_any default message', function(assert)
+{
+ var prefix = 'rule_if_any_default_message_';
+
+ var expectedMessage = 'value must be odd when over 6 or an array.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'if_any:value:min:6\\|array|regex:/[13579]$/' // odd
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 8});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = [1,2,3,4,5,6,7,8];
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 11;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = [1,2,3,4,5,6,7];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule if_any raw params', function(assert)
+{
+ var prefix = 'rule_if_any_raw_params_';
+
+ var expectedMessage = 'value must be odd when over 6 or an array.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: {
+ if_any: {
+ rules: 'min:6|array'
+ },
+ regex: /[13579]$/ // odd
+ }
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 8});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = [1,2,3,4,5,6,7,8];
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 11;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = [1,2,3,4,5,6,7];
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule if_any custom message
+// rule if_any aliased message
+// rule if_any override message
+// rule if_any invalid arguments
+// rule if_any raw params array
+// rule if_any default field
+
+test( 'rule if_not default message', function(assert)
+{
+ var prefix = 'rule_if_not_default_message_';
+
+ var expectedMessage = 'value must be odd when under 6.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'if_not:value:min:6|regex:/[13579]$/' // odd
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 4});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 2;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 3;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 7;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule if_not raw params', function(assert)
+{
+ var prefix = 'rule_if_not_raw_params_';
+
+ var expectedMessage = 'value must be odd when under 6.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: {
+ if_not: {
+ rules: {
+ min: 6
+ }
+ },
+ regex: /[13579]$/ // odd
+ }
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 4});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 2;
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 3;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 7;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+
+ a.value = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+// rule if_not custom message
+// rule if_not aliased message
+// rule if_not override message
+// rule if_not invalid arguments
+// rule if_not raw params array
+// rule if_not default field
+
+test( 'rule validate message', function(assert)
+{
+ var prefix = 'rule_validate_message_';
+
+ var expectedMessage = 'Tasks are required to have names.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'list_id'],
+ validation: {
+ rules: {
+ name: 'required'
+ }
+ }
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: 'validate:message'
+ },
+ messages: {
+ tasks: expectedMessage
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var a = new TaskList({
+ name: 'a',
+ tasks: [
+ {name: 't0'},
+ {name: null}
+ ]
+ });
+
+ var t0 = a.tasks[0];
+ var t1 = a.tasks[1];
+
+ strictEqual( t0.list_id, a.id );
+ strictEqual( t1.list_id, a.id );
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.name = 't1';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule validate models', function(assert)
+{
+ var prefix = 'rule_validate_models_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'list_id'],
+ validation: {
+ rules: {
+ name: 'required'
+ }
+ }
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: 'validate:models'
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var a = new TaskList({
+ name: 'a',
+ tasks: [
+ {name: 't0'},
+ {name: null}
+ ]
+ });
+
+ var t0 = a.tasks[0];
+ var t1 = a.tasks[1];
+ var expectedMessage = Rekord.collect( t1 );
+
+ strictEqual( t0.list_id, a.id );
+ strictEqual( t1.list_id, a.id );
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.name = 't1';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'rule validate validations', function(assert)
+{
+ var prefix = 'rule_validate_validations_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'list_id'],
+ validation: {
+ rules: {
+ name: 'required'
+ }
+ }
+ });
+
+ var TaskList = Rekord({
+ name: prefix + 'task_list',
+ fields: ['name'],
+ validation: {
+ rules: {
+ tasks: 'validate:validations'
+ }
+ },
+ hasMany: {
+ tasks: {
+ model: Task,
+ foreign: 'list_id'
+ }
+ }
+ });
+
+ var a = new TaskList({
+ name: 'a',
+ tasks: [
+ {name: 't0'},
+ {name: null}
+ ]
+ });
+
+ var t0 = a.tasks[0];
+ var t1 = a.tasks[1];
+
+ var expectedMessage = {}
+ expectedMessage[ t1.id ] = {
+ name: 'name is required.'
+ };
+
+ strictEqual( t0.list_id, a.id );
+ strictEqual( t1.list_id, a.id );
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {tasks: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ t1.name = 't1';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'custom validation', function(assert)
+{
+ var prefix = 'custom_validation_';
+
+ var expectedMessage = 'Name must start with a two character code followed by a colon followed by a description.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: '$isValidName'
+ }
+ },
+ methods: {
+ $isValidName: function(nm) {
+ if (nm && !/^\w\w\:.*$/.test(nm)) {
+ return expectedMessage;
+ }
+ }
+ }
+ });
+
+ var a = Task.create({name: 'not valid'});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 'TK: meow';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'validation required', function(assert)
+{
+ var prefix = 'validation_required_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: 'required'
+ },
+ required: true
+ }
+ });
+
+ var a = new Task();
+
+ strictEqual( a.$valid, void 0 );
+ notOk( a.$isSaved() );
+
+ a.$save();
+
+ strictEqual( a.$valid, false );
+ notOk( a.$isSaved() );
+
+ a.name = 'Hello World!';
+ a.$save();
+
+ strictEqual( a.$valid, true );
+ ok( a.$isSaved() );
+});
+
+test( 'rule specific message', function(assert)
+{
+ var prefix = 'rule_specific_message_';
+
+ var expectedMessage = 'Task name is required.'
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name'],
+ validation: {
+ rules: {
+ name: {
+ 'required': expectedMessage
+ }
+ }
+ }
+ });
+
+ var a = new Task();
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 't1';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'alias multiple message', function(assert)
+{
+ var prefix = 'alias_multiple_message_';
+
+ var expectedMessage = 'Task name must match task password.'
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'password'],
+ validation: {
+ rules: {
+ name: 'confirmed:password'
+ },
+ aliases: {
+ name: 'Task name',
+ password: 'task password'
+ }
+ }
+ });
+
+ var a = new Task({name: 0, password: 1});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {name: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.name = 1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'expression date', function(assert)
+{
+ var prefix = 'expression_date_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date']
+ });
+
+ var expr = Rekord.Validation.parseExpression( '10/03/2014', Task.Database );
+
+ ok( Rekord.isFunction( expr ) );
+
+ var millis = expr();
+
+ ok( Rekord.isNumber( millis ) );
+
+ var date = new Date( millis );
+
+ strictEqual( 9, date.getMonth() );
+ strictEqual( 3, date.getDate() );
+ strictEqual( 2014, date.getFullYear() );
+});
+
+test( 'expression field', function(assert)
+{
+ var prefix = 'expression_field_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date']
+ });
+
+ var expr = Rekord.Validation.parseExpression( 'name', Task.Database );
+
+ ok( Rekord.isFunction( expr ) );
+
+ var model = new Task({name: 'taskName'});
+
+ var name = expr( null, model );
+
+ strictEqual( name, 'taskName' );
+});
+
+test( 'expression relative', function(assert)
+{
+ var prefix = 'expression_relative_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date']
+ });
+
+ var expr = Rekord.Validation.parseExpression( '+1hr', Task.Database );
+
+ ok( Rekord.isFunction( expr ) );
+
+ var hourlater = expr();
+ var expected = Date.now() + 60 * 60 * 1000;
+
+ ok( Math.abs( hourlater - expected ) < 10 );
+});
+
+test( 'expression today', function(assert)
+{
+ var prefix = 'expression_today_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date']
+ });
+
+ var expr = Rekord.Validation.parseExpression( 'today', Task.Database );
+
+ ok( Rekord.isFunction( expr ) );
+
+ var exprTime = expr();
+ var expected = Rekord.startOfDay( new Date() ).getTime() + 0 * (24 * 60 * 60 * 1000);
+
+ ok( Math.abs( exprTime - expected ) < 10 );
+});
+
+test( 'expression tomorrow', function(assert)
+{
+ var prefix = 'expression_tomorrow_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date']
+ });
+
+ var expr = Rekord.Validation.parseExpression( 'tomorrow', Task.Database );
+
+ ok( Rekord.isFunction( expr ) );
+
+ var exprTime = expr();
+ var expected = Rekord.startOfDay( new Date() ).getTime() + 1 * (24 * 60 * 60 * 1000);
+
+ ok( Math.abs( exprTime - expected ) < 10 );
+});
+
+test( 'expression yesterday', function(assert)
+{
+ var prefix = 'expression_yesterday_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['name', 'due_date']
+ });
+
+ var expr = Rekord.Validation.parseExpression( 'yesterday', Task.Database );
+
+ ok( Rekord.isFunction( expr ) );
+
+ var exprTime = expr();
+ var expected = Rekord.startOfDay( new Date() ).getTime() - 1 * (24 * 60 * 60 * 1000);
+
+ ok( Math.abs( exprTime - expected ) < 10 );
+});
+
+test( 'transform ceil', function(assert)
+{
+ var prefix = 'transform_ceil_';
+
+ var expectedMessage = 'value must be over 3.5';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'ceil|min:3.5'
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 3});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 3.1;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'transform floor', function(assert)
+{
+ var prefix = 'transform_floor_';
+
+ var expectedMessage = 'value must be over 3.5';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'floor|min:3.5'
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 3.9});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'transform trim', function(assert)
+{
+ var prefix = 'transform_trim_';
+
+ var expectedMessage = 'value is required.';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'trim|required'
+ }
+ }
+ });
+
+ var a = Task.create({value: ' '});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 'a';
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'transform round', function(assert)
+{
+ var prefix = 'transform_round_';
+
+ var expectedMessage = 'value must be over 3.5';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'round|min:3.5'
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 3.4});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'transform mod', function(assert)
+{
+ var prefix = 'transform_mod_';
+
+ var expectedMessage = 'value must be even';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'mod:2|equal:0'
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: 3});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'transform abs', function(assert)
+{
+ var prefix = 'transform_abs_';
+
+ var expectedMessage = 'value must be over 3.5';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'abs|floor|min:3.5'
+ },
+ messages: {
+ value: expectedMessage
+ }
+ }
+ });
+
+ var a = Task.create({value: -3.9});
+
+ notOk( a.$validate() );
+ notOk( a.$valid );
+ deepEqual( a.$validations, {value: expectedMessage} );
+ deepEqual( a.$validationMessages, [expectedMessage] );
+
+ a.value = 4;
+
+ ok( a.$validate() );
+ ok( a.$valid );
+ deepEqual( a.$validations, {} );
+ deepEqual( a.$validationMessages, [] );
+});
+
+test( 'transform apply', function(assert)
+{
+ var prefix = 'transform_apply_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'abs|floor|apply'
+ }
+ }
+ });
+
+ var a = Task.create({value: '-3.9'});
+
+ ok( a.$validate() );
+
+ strictEqual( a.value, 3 );
+});
+
+test( 'transform filter', function(assert)
+{
+ var prefix = 'transform_filter_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'filter|apply'
+ }
+ }
+ });
+
+ var a = Task.create({value: [null, 1, 2, null, 3, 4, null]});
+
+ ok( a.$validate() );
+
+ deepEqual( a.value, [1, 2, 3, 4] );
+});
+
+test( 'transform startOfDay', function(assert)
+{
+ var prefix = 'transform_startOfDay_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'startOfDay'
+ }
+ }
+ });
+
+ var d = new Date();
+
+ var a = Task.create({value: d});
+
+ ok( a.$validate() );
+
+ strictEqual( a.value.getHours(), 0 );
+});
+
+test( 'transform endOfDay', function(assert)
+{
+ var prefix = 'transform_endOfDay_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'endOfDay'
+ }
+ }
+ });
+
+ var d = new Date();
+
+ var a = Task.create({value: d});
+
+ ok( a.$validate() );
+
+ strictEqual( a.value.getHours(), 23 );
+});
+
+test( 'transform base64', function(assert)
+{
+ var prefix = 'transform_base64_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'base64|apply'
+ }
+ }
+ });
+
+ var a = Task.create({value: 'Hello World'});
+
+ ok( a.$validate() );
+
+ strictEqual( a.value, 'SGVsbG8gV29ybGQ=' );
+});
+
+test( 'transform unbase64', function(assert)
+{
+ var prefix = 'transform_unbase64_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'unbase64|apply'
+ }
+ }
+ });
+
+ var a = Task.create({value: 'SGVsbG8gV29ybGQ='});
+
+ ok( a.$validate() );
+
+ strictEqual( a.value, 'Hello World' );
+});
+
+test( 'transform null', function(assert)
+{
+ var prefix = 'transform_null_';
+
+ var Task = Rekord({
+ name: prefix + 'task',
+ fields: ['value'],
+ validation: {
+ rules: {
+ value: 'if::equal:0|null'
+ }
+ }
+ });
+
+ var a = Task.create({value: 'notnull'});
+
+ ok( a.$validate() );
+
+ strictEqual( a.value, 'notnull' );
+
+ a.value = 0;
+
+ ok( a.$validate() );
+
+ strictEqual( a.value, null );
+});
+
+
+// transform null
diff --git a/test/index.html b/test/index.html
new file mode 100644
index 0000000..3ba9637
--- /dev/null
+++ b/test/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+