From 98174251f1b1273bbd123e2c383c3097edbd0681 Mon Sep 17 00:00:00 2001 From: ssh24 Date: Thu, 1 Jun 2017 11:00:44 -0400 Subject: [PATCH] Fix mixins/validatable docs --- lib/observer.js | 98 ++++++++-- lib/relations.js | 437 +++++++++++++++++++++++++++++++++++++++++++-- lib/transaction.js | 98 ++++++---- lib/validations.js | 123 ++++++++----- 4 files changed, 661 insertions(+), 95 deletions(-) diff --git a/lib/observer.js b/lib/observer.js index 3e493e28..d26e59e2 100644 --- a/lib/observer.js +++ b/lib/observer.js @@ -10,7 +10,7 @@ var utils = require('./utils'); module.exports = ObserverMixin; /** - * ObserverMixin class. Use to add observe/notifyObserversOf APIs to other + * ObserverMixin class. Use to add observe/notifyObserversOf APIs to other * classes. * * @class ObserverMixin @@ -20,12 +20,30 @@ function ObserverMixin() { /** * Register an asynchronous observer for the given operation (event). + * + * Example: + * + * Registers a `before save` observer for a given model. + * + * ```javascript + * MyModel.observe('before save', function filterProperties(ctx, next) { + if (ctx.options && ctx.options.skipPropertyFilter) return next(); + if (ctx.instance) { + FILTERED_PROPERTIES.forEach(function(p) { + ctx.instance.unsetAttribute(p); + }); + } else { + FILTERED_PROPERTIES.forEach(function(p) { + delete ctx.data[p]; + }); + } + next(); +}); + * ``` + * * @param {String} operation The operation name. * @callback {function} listener The listener function. It will be invoked with * `this` set to the model constructor, e.g. `User`. - * @param {Object} context Operation-specific context. - * @param {function(Error=)} next The callback to call when the observer - * has finished. * @end */ ObserverMixin.observe = function(operation, listener) { @@ -39,6 +57,16 @@ ObserverMixin.observe = function(operation, listener) { /** * Unregister an asynchronous observer for the given operation (event). + * + * Example: + * + * ```javascript + * MyModel.removeObserver('before save', function removedObserver(ctx, next) { + // some logic user want to apply to the removed observer... + next(); + }); + * ``` + * * @param {String} operation The operation name. * @callback {function} listener The listener function. * @end @@ -54,6 +82,15 @@ ObserverMixin.removeObserver = function(operation, listener) { /** * Unregister all asynchronous observers for the given operation (event). + * + * Example: + * + * Remove all observers connected to the `before save` operation. + * + * ```javascript + * MyModel.clearObservers('before save'); + * ``` + * * @param {String} operation The operation name. * @end */ @@ -65,10 +102,29 @@ ObserverMixin.clearObservers = function(operation) { /** * Invoke all async observers for the given operation(s). + * + * Example: + * + * Notify all async observers for the `before save` operation. + * + * ```javascript + * var context = { + Model: Model, + instance: obj, + isNewInstance: true, + hookState: hookState, + options: options, + }; + * Model.notifyObserversOf('before save', context, function(err) { + if (err) return cb(err); + // user can specify the logic after the observers have been notified + }); + * ``` + * * @param {String|String[]} operation The operation name(s). * @param {Object} context Operation-specific context. - * @param {function(Error=)} callback The callback to call when all observers - * has finished. + * @callback {function(Error=)} callback The callback to call when all observers + * have finished. */ ObserverMixin.notifyObserversOf = function(operation, context, callback) { var self = this; @@ -123,19 +179,41 @@ ObserverMixin._notifyBaseObservers = function(operation, context, callback) { }; /** - * Run the given function with before/after observers. It's done in three serial - * steps asynchronously: + * Run the given function with before/after observers. + * + * It's done in three serial asynchronous steps: * * - Notify the registered observers under 'before ' + operation * - Execute the function * - Notify the registered observers under 'after ' + operation * - * If an error happens, it fails fast and calls the callback with err. + * If an error happens, it fails first and calls the callback with err. + * + * Example: + * + * ```javascript + * var context = { + Model: Model, + instance: obj, + isNewInstance: true, + hookState: hookState, + options: options, + }; + * function work(done) { + process.nextTick(function() { + done(null, 1); + }); + } + * Model.notifyObserversAround('execute', context, work, function(err) { + if (err) return cb(err); + // user can specify the logic after the observers have been notified + }); + * ``` * * @param {String} operation The operation name * @param {Context} context The context object * @param {Function} fn The task to be invoked as fn(done) or fn(context, done) - * @param {Function} callback The callback function + * @callback {Function} callback The callback function * @returns {*} */ ObserverMixin.notifyObserversAround = function(operation, context, fn, callback) { diff --git a/lib/relations.js b/lib/relations.js index c7145c1a..e736117c 100644 --- a/lib/relations.js +++ b/lib/relations.js @@ -13,7 +13,7 @@ var RelationDefinition = relation.RelationDefinition; module.exports = RelationMixin; /** - * RelationMixin class. Use to define relationships between models. + * RelationMixin class. Use to define relationships between models. * * @class RelationMixin */ @@ -21,7 +21,7 @@ function RelationMixin() { } /** - * Define a "one to many" relationship by specifying the model name + * Define a "one to many" relationship by specifying the model name. * * Examples: * ``` @@ -50,25 +50,33 @@ function RelationMixin() { * // you can also call the Chapter.create method with the `chapters` property which will build a chapter * // instance and save the it in the data source. * book.chapters.create({name: 'Chapter 2'}, function(err, savedChapter) { - * // this callback is optional + * // this callback is optional * }); * * // Query chapters for the book - * book.chapters(function(err, chapters) { // all chapters with bookId = book.id + * book.chapters(function(err, chapters) { + * // all chapters with bookId = book.id * console.log(chapters); * }); * + * // Query chapters for the book with a filter * book.chapters({where: {name: 'test'}, function(err, chapters) { * // All chapters with bookId = book.id and name = 'test' * console.log(chapters); * }); * }); - *``` + * ``` + * * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. - * @options {Object} parameters Configuration parameters; see below. + * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} foreignKey Property name of foreign key field. - * @property {Object} model Model object + * @property {String} polymorphic Define a polymorphic relation name. + * @property {String} through Name of the through model. + * @property {String} keyThrough Property name of the foreign key in the through model. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Boolean} invert Specify if the relation is inverted. + * @property {Object} model The model object. */ RelationMixin.hasMany = function hasMany(modelTo, params) { return RelationDefinition.hasMany(this, modelTo, params); @@ -123,8 +131,12 @@ RelationMixin.hasMany = function hasMany(modelTo, params) { * @param {Class|String} modelTo Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} primaryKey Property name of primary key field. * @property {String} foreignKey Name of foreign key property. - * + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} properties Properties inherited from the parent object. + * @property {Object} options Property level options. + * @property {Boolean} options.invertProperties Specify if the properties should be inverted. */ RelationMixin.belongsTo = function(modelTo, params) { return RelationDefinition.belongsTo(this, modelTo, params); @@ -132,6 +144,7 @@ RelationMixin.belongsTo = function(modelTo, params) { /** * A hasAndBelongsToMany relation creates a direct many-to-many connection with another model, with no intervening model. + * * For example, if your application includes users and groups, with each group having many users and each user appearing * in many groups, you could declare the models this way: * ``` @@ -155,28 +168,432 @@ RelationMixin.belongsTo = function(modelTo, params) { * ``` * * @param {String|Object} modelTo Model object (or String name of model) to which you are creating the relationship. - * the relation * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} foreignKey Property name of foreign key field. - * @property {Object} model Model object + * @property {String} throughTable The table name of the through model. + * @property {String} through Name of the through model. + * @property {String} polymorphic Define a polymorphic relation name. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} model The model object. */ RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params) { return RelationDefinition.hasAndBelongsToMany(this, modelTo, params); }; +/** + * Define a "one to one" relationship by specifying the model name. + * + * Examples: + * ``` + * Supplier.hasOne(Account, {as: 'account', foreignKey: 'supplierId'}); + * ``` + * + * If the target model doesn’t have a foreign key property, LoopBack will add a property with the same name. + * + * The type of the property will be the same as the type of the target model’s id property. + * + * Please note the foreign key property is defined on the target model (in this example, Account). + * + * If you don’t specify them, then LoopBack derives the relation name and foreign key as follows: + * - Relation name: Camel case of the model name, for example, for the “supplier” model the relation is “supplier”. + * - Foreign key: The relation name appended with Id, for example, for relation name “supplier” the default foreign key is “supplierId”. + * + * Build a new account for the supplier with the supplierId to be set to the id of the supplier. + * ```js + * var supplier = supplier.account.build(data); + * ``` + * + * Create a new account for the supplier. If there is already an account, an error will be reported. + * ```js + * supplier.account.create(data, function(err, account) { + * ... + * }); + * ``` + * + * Find the supplier's account model. + * ```js + * supplier.account(function(err, account) { + * ... + * }); + * ``` + * + * Update the associated account. + * ```js + * supplier.account.update({balance: 100}, function(err, account) { + * ... + * }); + * ``` + * + * Remove the account for the supplier. + * ```js + * supplier.account.destroy(function(err) { + * ... + * }); + * ``` + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} primaryKey Property name of primary key field. + * @property {String} foreignKey Property name of foreign key field. + * @property {String} polymorphic Define a polymorphic relation name. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} model The model object. + * @property {Object} properties Properties inherited from the parent object. + * @property {Function} methods Scoped methods for the given relation. + */ RelationMixin.hasOne = function hasOne(modelTo, params) { return RelationDefinition.hasOne(this, modelTo, params); }; +/** + * References one or more instances of the target model. + * + * For example, a Customer model references one or more instances of the Account model. + * + * Define the relation in the model definition: + * + * - Definition of Customer model: + * ```json + * { + "name": "Customer", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "number" + } + }, + "validations": [], + "relations": { + "accounts": { + "type": "referencesMany", + "model": "Account", + "foreignKey": "accountIds", + "options": { + "validate": true, + "forceId": false + } + } + }, + "acls": [], + "methods": {} +} + * ``` + * + * - Definition of Account model: + * ```json + * { + "name": "Account", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "balance": { + "type": "number" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} + * ``` + * + * On the bootscript, create a customer instance and for that customer instance reference many account instances. + * + * For example: + * ```javascript + * var Customer = app.models.Customer; + var accounts = [ + { + name: 'Checking', + balance: 5000 + }, + { + name: 'Saving', + balance: 2000 + } + ]; + Customer.create({name: 'Mary Smith'}, function(err, customer) { + console.log('Customer:', customer); + async.each(accounts, function(account, done) { + customer.accounts.create(account, done); + }, function(err) { + console.log('Customer with accounts:', customer); + customer.accounts(console.log); + cb(err); + }); + }); + * ``` + * + * Sample referencesMany model data: + * ```javascript + * { + id: 1, + name: 'John Smith', + accounts: [ + "saving-01", "checking-01", + ] +} + * ``` + * + * Supported helper methods: + * - customer.accounts() + * - customer.accounts.create() + * - customer.accounts.build() + * - customer.accounts.findById() + * - customer.accounts.destroy() + * - customer.accounts.updateById() + * - customer.accounts.exists() + * - customer.accounts.add() + * - customer.accounts.remove() + * - customer.accounts.at() + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {Any} default The default value. + * @property {Object} options Options to specify for the relationship. + * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. + * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. + * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {String} foreignKey Property name of foreign key field. + * @property {Object} properties Properties inherited from the parent object. + * @property {Function} methods Scoped methods for the given relation. + */ RelationMixin.referencesMany = function referencesMany(modelTo, params) { return RelationDefinition.referencesMany(this, modelTo, params); }; +/** + * Represent a model that embeds another model. + * + * For example, a Customer embeds one billingAddress from the Address model. + * + * - Define the relation in bootscript: + * ```js + * Customer.embedsOne(Address, { + * as: 'address', // default to the relation name - address + * property: 'billingAddress' // default to addressItem + * }); + * ``` + * + * OR, define the relation in the model definition: + * + * - Definition of Customer model: + * ```json + * { + "name": "Customer", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "number" + } + }, + "validations": [], + "relations": { + "address": { + "type": "embedsOne", + "model": "Address", + "property": "billingAddress", + "options": { + "validate": true, + "forceId": false + } + } + }, + "acls": [], + "methods": {} +} + * ``` + * + * - Definition of Address model: + * ```json + * { + "name": "Address", + "base": "Model", + "idInjection": true, + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "zipCode": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} + * ``` + * + * Sample embedded model data: + * ```javascript + * { + id: 1, + name: 'John Smith', + billingAddress: { + street: '123 Main St', + city: 'San Jose', + state: 'CA', + zipCode: '95124' + } +} + * ``` + * + * Supported helper methods: + * - customer.address() + * - customer.address.build() + * - customer.address.create() + * - customer.address.update() + * - customer.address.destroy() + * - customer.address.value() + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} property Name of the property for the embedded item. + * @property {Any} default The default value. + * @property {Object} options Options to specify for the relationship. + * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. + * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. + * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} properties Properties inherited from the parent object. + * @property {Function} methods Scoped methods for the given relation. + */ RelationMixin.embedsOne = function embedsOne(modelTo, params) { return RelationDefinition.embedsOne(this, modelTo, params); }; +/** + * Represent a model that can embed many instances of another model. + * + * For example, a Customer can have multiple email addresses and each email address is a complex object that contains label and address. + * + * Define the relation code in bootscript: + * ```javascript + Customer.embedsMany(EmailAddress, { + as: 'emails', // default to the relation name - emailAddresses + property: 'emailList' // default to emailAddressItems + }); + * ``` + * + * OR, define the relation in the model definition: + * + * - Definition of Customer model: + * ```json + * { + "name": "Customer", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "number" + } + }, + "validations": [], + "relations": { + "emails": { + "type": "embedsMany", + "model": "EmailAddress", + "property": "emailList", + "options": { + "validate": true, + "forceId": false + } + } + }, + "acls": [], + "methods": {} +} + * ``` + * + * - Definition of EmailAddress model: + * ```json + * { + "name": "EmailAddress", + "base": "Model", + "idInjection": true, + "properties": { + "label": { + "type": "string" + }, + "address": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} + * ``` + * + * Sample embedded model data: + * ```javascript + * { + id: 1, + name: 'John Smith', + emails: [{ + label: 'work', + address: 'john@xyz.com' + }, { + label: 'home', + address: 'john@gmail.com' + }] +} + * ``` + * + * Supported helper methods: + * - customer.emails() + * - customer.emails.create() + * - customer.emails.build() + * - customer.emails.findById() + * - customer.emails.destroyById() + * - customer.emails.updateById() + * - customer.emails.exists() + * - customer.emails.add() + * - customer.emails.remove() + * - customer.emails.at() + * - customer.emails.value() + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} property Name of the property for the embedded item. + * @property {Any} default The default value. + * @property {Object} options Options to specify for the relationship. + * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. + * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. + * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. + * @property {String} polymorphic Define a polymorphic relation name. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} properties Properties inherited from the parent object. + * @property {Function} methods Scoped methods for the given relation. + */ RelationMixin.embedsMany = function embedsMany(modelTo, params) { return RelationDefinition.embedsMany(this, modelTo, params); }; diff --git a/lib/transaction.js b/lib/transaction.js index 26399786..05fed1a9 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -24,35 +24,9 @@ function TransactionMixin() { } /** - * Begin a new transaction - * @param {Object|String} [options] Options can be one of the forms: - * - Object: {isolationLevel: '...', timeout: 1000} - * - String: isolationLevel + * Begin a new transaction. * - * Valid values of `isolationLevel` are: - * - * - Transaction.READ_COMMITTED = 'READ COMMITTED'; // default - * - Transaction.READ_UNCOMMITTED = 'READ UNCOMMITTED'; - * - Transaction.SERIALIZABLE = 'SERIALIZABLE'; - * - Transaction.REPEATABLE_READ = 'REPEATABLE READ'; - * - * @param {Function} cb Callback function. It calls back with (err, transaction). - * To pass the transaction context to one of the CRUD methods, use the `options` - * argument with `transaction` property, for example, - * - * ```js - * - * MyModel.beginTransaction('READ COMMITTED', function(err, tx) { - * MyModel.create({x: 1, y: 'a'}, {transaction: tx}, function(err, inst) { - * MyModel.find({x: 1}, {transaction: tx}, function(err, results) { - * // ... - * tx.commit(function(err) {...}); - * }); - * }); - * }); - * ``` - * - * The transaction can be committed or rolled back. If timeout happens, the + * A transaction can be committed or rolled back. If timeout happens, the * transaction will be rolled back. Please note a transaction is typically * associated with a pooled connection. Committing or rolling back a transaction * will release the connection back to the pool. @@ -65,6 +39,36 @@ function TransactionMixin() { * source/connector instance. CRUD methods will not join the current transaction * if its model is not attached the same data source. * + * Example: + * + * To pass the transaction context to one of the CRUD methods, use the `options` + * argument with `transaction` property, for example, + * + * ```js + * MyModel.beginTransaction('READ COMMITTED', function(err, tx) { + * MyModel.create({x: 1, y: 'a'}, {transaction: tx}, function(err, inst) { + * MyModel.find({x: 1}, {transaction: tx}, function(err, results) { + * // ... + * tx.commit(function(err) {...}); + * }); + * }); + * }); + * ``` + * + * @param {Object|String} options Options to be passed upon transaction. + * + * Can be one of the forms: + * - Object: {isolationLevel: '...', timeout: 1000} + * - String: isolationLevel + * + * Valid values of `isolationLevel` are: + * + * - Transaction.READ_COMMITTED = 'READ COMMITTED'; // default + * - Transaction.READ_UNCOMMITTED = 'READ UNCOMMITTED'; + * - Transaction.SERIALIZABLE = 'SERIALIZABLE'; + * - Transaction.REPEATABLE_READ = 'REPEATABLE READ'; + * @callback {Function} cb Callback function. + * @returns {Promise|undefined} Returns a callback promise. */ TransactionMixin.beginTransaction = function(options, cb) { cb = cb || utils.createPromiseCallback(); @@ -107,9 +111,22 @@ TransactionMixin.beginTransaction = function(options, cb) { if (Transaction) { jutil.mixin(Transaction.prototype, ObserverMixin); /** - * Commit a transaction and release it back to the pool - * @param {Function} cb Callback function - * @returns {Promise|undefined} + * Commit a transaction and release it back to the pool. + * + * Example: + * + * ```js + * MyModel.beginTransaction('READ COMMITTED', function(err, tx) { + * // some crud operation of your choice + * tx.commit(function(err) { + * // release the connection pool upon committing + * tx.close(err); + * }); + * }); + * ``` + * + * @callback {Function} cb Callback function. + * @returns {Promise|undefined} Returns a callback promise. */ Transaction.prototype.commit = function(cb) { var self = this; @@ -141,9 +158,22 @@ if (Transaction) { }; /** - * Rollback a transaction and release it back to the pool - * @param {Function} cb Callback function - * @returns {Promise|undefined} + * Rollback a transaction and release it back to the pool. + * + * Example: + * + * ```js + * MyModel.beginTransaction('READ COMMITTED', function(err, tx) { + * // some crud operation of your choice + * tx.rollback(function(err) { + * // release the connection pool upon committing + * tx.close(err); + * }); + * }); + * ``` + * + * @callback {Function} cb Callback function. + * @returns {Promise|undefined} Returns a callback promise. */ Transaction.prototype.rollback = function(cb) { var self = this; diff --git a/lib/validations.js b/lib/validations.js index 6a99f1db..ad4b29f7 100644 --- a/lib/validations.js +++ b/lib/validations.js @@ -32,6 +32,7 @@ function Validatable() { /** * Validate presence of one or more specified properties. + * * Requires a model to include a property to be considered valid; fails when validated field is blank. * * For example, validate presence of title @@ -48,34 +49,43 @@ function Validatable() { * ``` * * @param {String} propertyName One or more property names. - * @options {Object} errMsg Optional custom error message. Default is "can't be blank" + * @options {Object} options Configuration parameters; see below. * @property {String} message Error message to use instead of default. + * @property {String} if Validate only if `if` exists. + * @property {String} unless Validate only if `unless` exists. */ Validatable.validatesPresenceOf = getConfigurator('presence'); /** * Validate absence of one or more specified properties. - * A model should not include a property to be considered valid; fails when validated field not blank. + * + * A model should not include a property to be considered valid; fails when validated field is not blank. * * For example, validate absence of reserved * ``` * Post.validatesAbsenceOf('reserved', { unless: 'special' }); * ``` + * * @param {String} propertyName One or more property names. - * @options {Object} errMsg Optional custom error message. Default is "can't be set" + * @options {Object} options Configuration parameters; see below. * @property {String} message Error message to use instead of default. + * @property {String} if Validate only if `if` exists. + * @property {String} unless Validate only if `unless` exists. */ Validatable.validatesAbsenceOf = getConfigurator('absence'); /** - * Validate length. Require a property length to be within a specified range. - * Three kinds of validations: min, max, is. + * Validate length. + * + * Require a property length to be within a specified range. + * + * There are three kinds of validations: min, max, is. * * Default error messages: * * - min: too short * - max: too long - * - is: length is wrong + * - is: length is wrong * * Example: length validations * ``` @@ -89,36 +99,42 @@ Validatable.validatesAbsenceOf = getConfigurator('absence'); * User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}}); * User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}}); * ``` + * * @param {String} propertyName Property name to validate. - * @options {Object} Options See below. + * @options {Object} options Configuration parameters; see below. * @property {Number} is Value that property must equal to validate. * @property {Number} min Value that property must be less than to be valid. * @property {Number} max Value that property must be less than to be valid. - * @property {Object} message Optional Object with string properties for custom error message for each validation: is, min, or max + * @property {Object} message Optional object with string properties for custom error message for each validation: is, min, or max. */ Validatable.validatesLengthOf = getConfigurator('length'); /** - * Validate numericality. Requires a value for property to be either an integer or number. + * Validate numericality. + * + * Requires a value for property to be either an integer or number. * * Example * ``` - * User.validatesNumericalityOf('age', { message: { number: '...' }}); - * User.validatesNumericalityOf('age', {int: true, message: { int: '...' }}); + * User.validatesNumericalityOf('age', { message: { number: 'is not a number' }}); + * User.validatesNumericalityOf('age', {int: true, message: { int: 'is not an integer' }}); * ``` * * @param {String} propertyName Property name to validate. - * @options {Object} Options See below. + * @options {Object} options Configuration parameters; see below. * @property {Boolean} int If true, then property must be an integer to be valid. - * @property {Object} message Optional object with string properties for 'int' for integer validation. Default error messages: - * + * @property {Boolean} allowBlank Allow property to be blank. + * @property {Boolean} allowNull Allow property to be null. + * @property {Object} message Optional object with string properties for 'int' for integer validation. Default error messages: * - number: is not a number * - int: is not an integer */ Validatable.validatesNumericalityOf = getConfigurator('numericality'); /** - * Validate inclusion in set. Require a value for property to be in the specified array. + * Validate inclusion in set. + * + * Require a value for property to be in the specified array. * * Example: * ``` @@ -129,8 +145,8 @@ Validatable.validatesNumericalityOf = getConfigurator('numericality'); * ``` * * @param {String} propertyName Property name to validate. - * @options {Object} Options See below - * @property {Array} inArray Property must match one of the values in the array to be valid. + * @options {Object} options Configuration parameters; see below. + * @property {Array} in Property must match one of the values in the array to be valid. * @property {String} message Optional error message if property is not valid. * Default error message: "is not included in the list". * @property {Boolean} allowNull Whether null values are allowed. @@ -138,28 +154,31 @@ Validatable.validatesNumericalityOf = getConfigurator('numericality'); Validatable.validatesInclusionOf = getConfigurator('inclusion'); /** - * Validate exclusion. Require a property value not be in the specified array. + * Validate exclusion in a set. + * + * Require a property value not be in the specified array. * * Example: `Company.validatesExclusionOf('domain', {in: ['www', 'admin']});` * * @param {String} propertyName Property name to validate. - * @options {Object} Options + * @options {Object} options Configuration parameters; see below. * @property {Array} in Property must not match any of the values in the array to be valid. - * @property {String} message Optional error message if property is not valid. Default error message: "is reserved". + * @property {String} message Optional error message if property is not valid. Default error message: "is reserved". * @property {Boolean} allowNull Whether null values are allowed. */ Validatable.validatesExclusionOf = getConfigurator('exclusion'); /** - * Validate format. Require a model to include a property that matches the given format. + * Validate format. * - * Require a model to include a property that matches the given format. Example: - * `User.validatesFormatOf('name', {with: /\w+/});` + * Require a model to include a property that matches the given format. + * + * Example: `User.validatesFormatOf('name', {with: /\w+/});` * * @param {String} propertyName Property name to validate. - * @options {Object} Options + * @options {Object} options Configuration parameters; see below. * @property {RegExp} with Regular expression to validate format. - * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". + * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". * @property {Boolean} allowNull Whether null values are allowed. */ Validatable.validatesFormatOf = getConfigurator('format'); @@ -168,7 +187,7 @@ Validatable.validatesFormatOf = getConfigurator('format'); * Validate using custom validation function. * * Example: - * + *```javascript * User.validate('name', customValidator, {message: 'Bad name'}); * function customValidator(err) { * if (this.name === 'bad') err(); @@ -177,11 +196,12 @@ Validatable.validatesFormatOf = getConfigurator('format'); * user.isValid(); // true * user.name = 'bad'; * user.isValid(); // false + * ``` * * @param {String} propertyName Property name to validate. * @param {Function} validatorFn Custom validation function. - * @options {Object} Options See below. - * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". + * @options {Object} options Configuration parameters; see below. + * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". * @property {Boolean} allowNull Whether null values are allowed. */ Validatable.validate = getConfigurator('custom'); @@ -189,7 +209,6 @@ Validatable.validate = getConfigurator('custom'); /** * Validate using custom asynchronous validation function. * - * * Example: *```js * User.validateAsync('name', customValidator, {message: 'Bad name'}); @@ -209,17 +228,19 @@ Validatable.validate = getConfigurator('custom'); * user.isValid(function (isValid) { * isValid; // false * }) - *``` + * ``` + * * @param {String} propertyName Property name to validate. * @param {Function} validatorFn Custom validation function. - * @options {Object} Options See below - * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". + * @options {Object} options Configuration parameters; see below. + * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". * @property {Boolean} allowNull Whether null values are allowed. */ Validatable.validateAsync = getConfigurator('custom', {async: true}); /** - * Validate uniqueness. Ensure the value for property is unique in the collection of models. + * Validate uniqueness of the value for a property in the collection of models. + * * Not available for all connectors. Currently supported with these connectors: * - In Memory * - Oracle @@ -233,18 +254,33 @@ Validatable.validateAsync = getConfigurator('custom', {async: true}); * // The login must be unique within each Site. * SiteUser.validateUniquenessOf('login', { scopedTo: ['siteId'] }); * ``` - + * * @param {String} propertyName Property name to validate. - * @options {Object} Options See below. + * @options {Object} options Configuration parameters; see below. * @property {RegExp} with Regular expression to validate format. * @property {Array.} scopedTo List of properties defining the scope. - * @property {String} message Optional error message if property is not valid. Default error message: "is not unique". + * @property {String} message Optional error message if property is not valid. Default error message: "is not unique". * @property {Boolean} allowNull Whether null values are allowed. - * @property {String} ignoreCase Make the validation case insensitive + * @property {String} ignoreCase Make the validation case insensitive. + * @property {String} if Validate only if `if` exists. + * @property {String} unless Validate only if `unless` exists. */ Validatable.validatesUniquenessOf = getConfigurator('uniqueness', {async: true}); +/** + * Validate if a value for a property is a Date. + * + * Example + * ``` + * User.validatesDateOf('today', {message: 'today is not a date!'}); + * ``` + * + * @param {String} propertyName Property name to validate. + * @options {Object} options Configuration parameters; see below. + * @property {String} message Error message to use instead of default. + */ Validatable.validatesDateOf = getConfigurator('date'); + // implementation of validators /*! @@ -438,15 +474,15 @@ function getConfigurator(name, opts) { * NOTE: This method can be called as synchronous only when no asynchronous validation is * configured. It's strongly recommended to run all validations as asyncronous. * - * Example: ExpressJS controller: render user if valid, show flash otherwise - * ``` + * Example: ExpressJS controller - render user if valid, show flash otherwise + * ```javascript * user.isValid(function (valid) { * if (valid) res.render({user: user}); * else res.flash('error', 'User is not valid'), console.log(user.errors), res.redirect('/users'); * }); * ``` * Another example: - * ``` + * ```javascript * user.isValid(function (valid) { * if (!valid) { * console.log(user.errors); @@ -458,7 +494,9 @@ function getConfigurator(name, opts) { * } * }); * ``` - * @param {Function} callback called with (valid) + * @callback {Function} callback Called with (valid). + * @param {Object} data Data to be validated. + * @param {Object} options Options to be specified upon validation. * @returns {Boolean} True if no asynchronous validation is configured and all properties pass validation. */ Validatable.prototype.isValid = function(callback, data, options) { @@ -663,6 +701,7 @@ var defaultMessages = { /** * Checks if attribute is undefined or null. Calls err function with 'blank' or 'null'. * See defaultMessages. You can affect this behaviour with conf.allowBlank and conf.allowNull. + * @private * @param {String} attr Property name of attribute * @param {Object} conf conf object for validator * @param {Function} err @@ -807,6 +846,8 @@ function ErrorCodes(messages) { * callback(err); * } * ``` + * + * @private */ function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj);