Merge pull request #1395 from strongloop/fix/api-docs-mixins-validatable

Fix mixin and validatable docs
This commit is contained in:
Sakib Hasan 2017-06-06 14:46:21 -04:00 committed by GitHub
commit 3b45c76d0f
4 changed files with 661 additions and 95 deletions

View File

@ -20,12 +20,30 @@ function ObserverMixin() {
/** /**
* Register an asynchronous observer for the given operation (event). * 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. * @param {String} operation The operation name.
* @callback {function} listener The listener function. It will be invoked with * @callback {function} listener The listener function. It will be invoked with
* `this` set to the model constructor, e.g. `User`. * `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 * @end
*/ */
ObserverMixin.observe = function(operation, listener) { ObserverMixin.observe = function(operation, listener) {
@ -39,6 +57,16 @@ ObserverMixin.observe = function(operation, listener) {
/** /**
* Unregister an asynchronous observer for the given operation (event). * 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. * @param {String} operation The operation name.
* @callback {function} listener The listener function. * @callback {function} listener The listener function.
* @end * @end
@ -54,6 +82,15 @@ ObserverMixin.removeObserver = function(operation, listener) {
/** /**
* Unregister all asynchronous observers for the given operation (event). * 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. * @param {String} operation The operation name.
* @end * @end
*/ */
@ -65,10 +102,29 @@ ObserverMixin.clearObservers = function(operation) {
/** /**
* Invoke all async observers for the given operation(s). * 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 {String|String[]} operation The operation name(s).
* @param {Object} context Operation-specific context. * @param {Object} context Operation-specific context.
* @param {function(Error=)} callback The callback to call when all observers * @callback {function(Error=)} callback The callback to call when all observers
* has finished. * have finished.
*/ */
ObserverMixin.notifyObserversOf = function(operation, context, callback) { ObserverMixin.notifyObserversOf = function(operation, context, callback) {
var self = this; 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 * Run the given function with before/after observers.
* steps asynchronously: *
* It's done in three serial asynchronous steps:
* *
* - Notify the registered observers under 'before ' + operation * - Notify the registered observers under 'before ' + operation
* - Execute the function * - Execute the function
* - Notify the registered observers under 'after ' + operation * - 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 {String} operation The operation name
* @param {Context} context The context object * @param {Context} context The context object
* @param {Function} fn The task to be invoked as fn(done) or fn(context, done) * @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 {*} * @returns {*}
*/ */
ObserverMixin.notifyObserversAround = function(operation, context, fn, callback) { ObserverMixin.notifyObserversAround = function(operation, context, fn, callback) {

View File

@ -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: * Examples:
* ``` * ```
@ -54,21 +54,29 @@ function RelationMixin() {
* }); * });
* *
* // Query chapters for the book * // 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); * console.log(chapters);
* }); * });
* *
* // Query chapters for the book with a filter
* book.chapters({where: {name: 'test'}, function(err, chapters) { * book.chapters({where: {name: 'test'}, function(err, chapters) {
* // All chapters with bookId = book.id and name = 'test' * // All chapters with bookId = book.id and name = 'test'
* console.log(chapters); * console.log(chapters);
* }); * });
* }); * });
*``` * ```
*
* @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. * @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} 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 {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) { RelationMixin.hasMany = function hasMany(modelTo, params) {
return RelationDefinition.hasMany(this, 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. * @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. * @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} 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 {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) { RelationMixin.belongsTo = function(modelTo, params) {
return RelationDefinition.belongsTo(this, 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. * 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 * 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: * 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. * @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. * @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} 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 {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) { RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params) {
return RelationDefinition.hasAndBelongsToMany(this, 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 doesnt 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 models id property.
*
* Please note the foreign key property is defined on the target model (in this example, Account).
*
* If you dont 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) { RelationMixin.hasOne = function hasOne(modelTo, params) {
return RelationDefinition.hasOne(this, 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) { RelationMixin.referencesMany = function referencesMany(modelTo, params) {
return RelationDefinition.referencesMany(this, 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) { RelationMixin.embedsOne = function embedsOne(modelTo, params) {
return RelationDefinition.embedsOne(this, 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) { RelationMixin.embedsMany = function embedsMany(modelTo, params) {
return RelationDefinition.embedsMany(this, modelTo, params); return RelationDefinition.embedsMany(this, modelTo, params);
}; };

View File

@ -24,35 +24,9 @@ function TransactionMixin() {
} }
/** /**
* Begin a new transaction * Begin a new transaction.
* @param {Object|String} [options] Options can be one of the forms:
* - Object: {isolationLevel: '...', timeout: 1000}
* - String: isolationLevel
* *
* Valid values of `isolationLevel` are: * A transaction can be committed or rolled back. If timeout happens, the
*
* - 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
* transaction will be rolled back. Please note a transaction is typically * transaction will be rolled back. Please note a transaction is typically
* associated with a pooled connection. Committing or rolling back a transaction * associated with a pooled connection. Committing or rolling back a transaction
* will release the connection back to the pool. * 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 * source/connector instance. CRUD methods will not join the current transaction
* if its model is not attached the same data source. * 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) { TransactionMixin.beginTransaction = function(options, cb) {
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
@ -107,9 +111,22 @@ TransactionMixin.beginTransaction = function(options, cb) {
if (Transaction) { if (Transaction) {
jutil.mixin(Transaction.prototype, ObserverMixin); jutil.mixin(Transaction.prototype, ObserverMixin);
/** /**
* Commit a transaction and release it back to the pool * Commit a transaction and release it back to the pool.
* @param {Function} cb Callback function *
* @returns {Promise|undefined} * 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) { Transaction.prototype.commit = function(cb) {
var self = this; var self = this;
@ -141,9 +158,22 @@ if (Transaction) {
}; };
/** /**
* Rollback a transaction and release it back to the pool * Rollback a transaction and release it back to the pool.
* @param {Function} cb Callback function *
* @returns {Promise|undefined} * 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) { Transaction.prototype.rollback = function(cb) {
var self = this; var self = this;

View File

@ -32,6 +32,7 @@ function Validatable() {
/** /**
* Validate presence of one or more specified properties. * 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. * Requires a model to include a property to be considered valid; fails when validated field is blank.
* *
* For example, validate presence of title * For example, validate presence of title
@ -48,28 +49,37 @@ function Validatable() {
* ``` * ```
* *
* @param {String} propertyName One or more property names. * @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} 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'); Validatable.validatesPresenceOf = getConfigurator('presence');
/** /**
* Validate absence of one or more specified properties. * 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 * For example, validate absence of reserved
* ``` * ```
* Post.validatesAbsenceOf('reserved', { unless: 'special' }); * Post.validatesAbsenceOf('reserved', { unless: 'special' });
* ``` * ```
*
* @param {String} propertyName One or more property names. * @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} 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'); Validatable.validatesAbsenceOf = getConfigurator('absence');
/** /**
* Validate length. Require a property length to be within a specified range. * Validate length.
* Three kinds of validations: min, max, is. *
* Require a property length to be within a specified range.
*
* There are three kinds of validations: min, max, is.
* *
* Default error messages: * Default error messages:
* *
@ -89,36 +99,42 @@ Validatable.validatesAbsenceOf = getConfigurator('absence');
* User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}}); * User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}});
* User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}}); * User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}});
* ``` * ```
*
* @param {String} propertyName Property name to validate. * @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} is Value that property must equal to validate.
* @property {Number} min Value that property must be less than to be valid. * @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 {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'); 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 * Example
* ``` * ```
* User.validatesNumericalityOf('age', { message: { number: '...' }}); * User.validatesNumericalityOf('age', { message: { number: 'is not a number' }});
* User.validatesNumericalityOf('age', {int: true, message: { int: '...' }}); * User.validatesNumericalityOf('age', {int: true, message: { int: 'is not an integer' }});
* ``` * ```
* *
* @param {String} propertyName Property name to validate. * @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 {Boolean} int If true, then property must be an integer to be valid.
* @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: * @property {Object} message Optional object with string properties for 'int' for integer validation. Default error messages:
*
* - number: is not a number * - number: is not a number
* - int: is not an integer * - int: is not an integer
*/ */
Validatable.validatesNumericalityOf = getConfigurator('numericality'); 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: * Example:
* ``` * ```
@ -129,8 +145,8 @@ Validatable.validatesNumericalityOf = getConfigurator('numericality');
* ``` * ```
* *
* @param {String} propertyName Property name to validate. * @param {String} propertyName Property name to validate.
* @options {Object} Options See below * @options {Object} options Configuration parameters; see below.
* @property {Array} inArray Property must match one of the values in the array to be valid. * @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. * @property {String} message Optional error message if property is not valid.
* Default error message: "is not included in the list". * Default error message: "is not included in the list".
* @property {Boolean} allowNull Whether null values are allowed. * @property {Boolean} allowNull Whether null values are allowed.
@ -138,12 +154,14 @@ Validatable.validatesNumericalityOf = getConfigurator('numericality');
Validatable.validatesInclusionOf = getConfigurator('inclusion'); 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']});` * Example: `Company.validatesExclusionOf('domain', {in: ['www', 'admin']});`
* *
* @param {String} propertyName Property name to validate. * @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 {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. * @property {Boolean} allowNull Whether null values are allowed.
@ -151,13 +169,14 @@ Validatable.validatesInclusionOf = getConfigurator('inclusion');
Validatable.validatesExclusionOf = getConfigurator('exclusion'); 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: * Require a model to include a property that matches the given format.
* `User.validatesFormatOf('name', {with: /\w+/});` *
* Example: `User.validatesFormatOf('name', {with: /\w+/});`
* *
* @param {String} propertyName Property name to validate. * @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 {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. * @property {Boolean} allowNull Whether null values are allowed.
@ -168,7 +187,7 @@ Validatable.validatesFormatOf = getConfigurator('format');
* Validate using custom validation function. * Validate using custom validation function.
* *
* Example: * Example:
* *```javascript
* User.validate('name', customValidator, {message: 'Bad name'}); * User.validate('name', customValidator, {message: 'Bad name'});
* function customValidator(err) { * function customValidator(err) {
* if (this.name === 'bad') err(); * if (this.name === 'bad') err();
@ -177,10 +196,11 @@ Validatable.validatesFormatOf = getConfigurator('format');
* user.isValid(); // true * user.isValid(); // true
* user.name = 'bad'; * user.name = 'bad';
* user.isValid(); // false * user.isValid(); // false
* ```
* *
* @param {String} propertyName Property name to validate. * @param {String} propertyName Property name to validate.
* @param {Function} validatorFn Custom validation function. * @param {Function} validatorFn Custom validation function.
* @options {Object} Options See below. * @options {Object} options Configuration parameters; see below.
* @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. * @property {Boolean} allowNull Whether null values are allowed.
*/ */
@ -189,7 +209,6 @@ Validatable.validate = getConfigurator('custom');
/** /**
* Validate using custom asynchronous validation function. * Validate using custom asynchronous validation function.
* *
*
* Example: * Example:
*```js *```js
* User.validateAsync('name', customValidator, {message: 'Bad name'}); * User.validateAsync('name', customValidator, {message: 'Bad name'});
@ -209,17 +228,19 @@ Validatable.validate = getConfigurator('custom');
* user.isValid(function (isValid) { * user.isValid(function (isValid) {
* isValid; // false * isValid; // false
* }) * })
*``` * ```
*
* @param {String} propertyName Property name to validate. * @param {String} propertyName Property name to validate.
* @param {Function} validatorFn Custom validation function. * @param {Function} validatorFn Custom validation function.
* @options {Object} Options See below * @options {Object} options Configuration parameters; see below.
* @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. * @property {Boolean} allowNull Whether null values are allowed.
*/ */
Validatable.validateAsync = getConfigurator('custom', {async: true}); 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: * Not available for all connectors. Currently supported with these connectors:
* - In Memory * - In Memory
* - Oracle * - Oracle
@ -233,18 +254,33 @@ Validatable.validateAsync = getConfigurator('custom', {async: true});
* // The login must be unique within each Site. * // The login must be unique within each Site.
* SiteUser.validateUniquenessOf('login', { scopedTo: ['siteId'] }); * SiteUser.validateUniquenessOf('login', { scopedTo: ['siteId'] });
* ``` * ```
*
* @param {String} propertyName Property name to validate. * @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 {RegExp} with Regular expression to validate format.
* @property {Array.<String>} scopedTo List of properties defining the scope. * @property {Array.<String>} scopedTo List of properties defining the scope.
* @property {String} message Optional error message if property is not valid. Default error message: "is not unique". * @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 {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}); 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'); Validatable.validatesDateOf = getConfigurator('date');
// implementation of validators // 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 * 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. * 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) { * user.isValid(function (valid) {
* if (valid) res.render({user: user}); * if (valid) res.render({user: user});
* else res.flash('error', 'User is not valid'), console.log(user.errors), res.redirect('/users'); * else res.flash('error', 'User is not valid'), console.log(user.errors), res.redirect('/users');
* }); * });
* ``` * ```
* Another example: * Another example:
* ``` * ```javascript
* user.isValid(function (valid) { * user.isValid(function (valid) {
* if (!valid) { * if (!valid) {
* console.log(user.errors); * 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. * @returns {Boolean} True if no asynchronous validation is configured and all properties pass validation.
*/ */
Validatable.prototype.isValid = function(callback, data, options) { 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'. * 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. * See defaultMessages. You can affect this behaviour with conf.allowBlank and conf.allowNull.
* @private
* @param {String} attr Property name of attribute * @param {String} attr Property name of attribute
* @param {Object} conf conf object for validator * @param {Object} conf conf object for validator
* @param {Function} err * @param {Function} err
@ -807,6 +846,8 @@ function ErrorCodes(messages) {
* callback(err); * callback(err);
* } * }
* ``` * ```
*
* @private
*/ */
function ValidationError(obj) { function ValidationError(obj) {
if (!(this instanceof ValidationError)) return new ValidationError(obj); if (!(this instanceof ValidationError)) return new ValidationError(obj);