不要怂,就是干,撸起袖子干!

Commit a76f1c75 by Mick Hansen

Merge pull request #1887 from sequelize/virtualAttr

Support for virtual attributes 
2 parents b73d1f91 95a65ac7
"use strict";
var markdox = require('markdox')
, program = require('commander')
, fs = require('fs')
, _ = require('lodash');
program
.version('0.0.1')
.option('-f, --file [file]', 'Process a single file', '')
.option('-a, --all', 'Process all files, generate index etc. (default if no options are specified')
.option('-c, --clean', 'Remove all generated markdown and HTML files')
.option('--html', 'Generate html files from the markdown ones (requires manual installation of the github-flavored-markdown package')
.parse(process.argv);
if (program.clean) {
fs.readdirSync('docs/').forEach(function (file) {
if (file.indexOf('.ejs') === -1 && file.indexOf('.js') === -1) {
fs.unlinkSync ('docs/' + file);
}
});
return ;
}
if (program.html) {
var ghm = require('github-flavored-markdown');
}
var getTag = function(tags, tagName) {
return _.find(tags, function (tag) {
return tag.type === tagName;
});
};
var getTags = function(tags, tagName) {
return _.where(tags, function (tag) {
return tag.type === tagName;
});
};
var options = {
formatter: function (docfile) {
docfile = markdox.defaultFormatter(docfile);
docfile.members = [];
docfile.javadoc.forEach(function(javadoc){
// Find constructor tags
javadoc.isConstructor = getTag(javadoc.raw.tags, 'constructor') !== undefined;
javadoc.isMixin = getTag(javadoc.raw.tags, 'mixin') !== undefined;
javadoc.isProperty = getTag(javadoc.raw.tags, 'property') !== undefined;
javadoc.private = getTag(javadoc.raw.tags, 'private') !== undefined;
javadoc.since = getTag(javadoc.raw.tags, 'since');
// Only show params without a dot in them (dots means attributes of object, so no need to clutter the signature too much)
var params = [] ;
javadoc.paramTags.forEach(function (paramTag) {
if (paramTag.name.indexOf('.') === -1) {
params.push(paramTag.name);
}
});
javadoc.paramStr = (javadoc.isMethod || javadoc.isFunction) ? '(' + params.join(', ') + ')' : '';
// Convert | to | to be able to use github flavored md tables
if (javadoc.paramTags) {
javadoc.paramTags.forEach(function (paramTag) {
paramTag.joinedTypes = paramTag.joinedTypes.replace(/\|/g, '|');
});
}
// Handle aliases
javadoc.aliases = getTags(javadoc.raw.tags, 'alias').map(function (a) {
return a.string;
}).join(', ');
// Handle deprecation text
if (javadoc.deprecated) {
var deprecation = getTag(javadoc.raw.tags, 'deprecated');
javadoc.deprecated = deprecation.string;
}
// Handle linking in comments
javadoc.see = getTags(javadoc.raw.tags, 'see');
javadoc.see.forEach(function (see, i, collection) {
collection[i] = {};
if (see.local) {
collection[i].external = false;
if (see.local.indexOf('{') === 0){
var _see = see.local.split('}');
_see[0] = _see[0].substring(1);
collection[i].url = 'API-Reference-' + _see[0];
collection[i].text = see.local.replace(/{|}/g, '');
} else {
collection[i].url = false;
collection[i].text = see.local;
}
} else {
see.external = true;
see.text = see.url;
collection[i] = see;
}
});
// Set a name for properties
if (!javadoc.name) {
var property = getTag(javadoc.raw.tags, 'property');
if (property) {
javadoc.name = property.string;
}
}
if (javadoc.isMixin) {
javadoc.name = getTag(javadoc.raw.tags, 'mixin').string;
}
javadoc.mixes = getTags(javadoc.raw.tags, 'mixes').map(function (mix) {
return {
text: mix.string,
link: (mix.string.indexOf('www') !== -1 || mix.string.indexOf('http') !== -1) ? mix.string: 'API-Reference-' + mix.string
};
});
if (!javadoc.isClass) {
if (!javadoc.isProperty) {
docfile.members.push({
text: javadoc.name + javadoc.paramStr,
link: '#' + javadoc.name
});
} else {
docfile.members.push({
text: javadoc.name,
link: '#' + javadoc.name
});
}
}
});
return docfile;
},
template: 'docs/output.md.ejs'
};
var files;
if (program.file) {
files = [{file: program.file, output: 'tmp'}];
} else {
files = [
{file:'lib/sequelize.js', output: 'API-Reference-Sequelize'},
{file:'lib/instance.js', output: 'API-Reference-Instance'},
{file:'lib/model.js', output: 'API-Reference-Model'},
{file:'lib/query-chainer.js', output: 'API-Reference-QueryChainer'},
{file:'lib/hooks.js', output: 'API-Reference-Hooks'},
{file:'lib/associations/mixin.js', output: 'API-Reference-Associations'},
{file:'lib/promise.js', output: 'API-Reference-Promise'},
{file:'lib/transaction.js', output: 'API-Reference-Transaction'},
{file:'lib/data-types.js', output: 'API-Reference-DataTypes'}
];
}
files.forEach(function (file) {
var opts = _.clone(options)
, output = 'docs/' + file.output + '.md';
opts.output = output;
markdox.process(file.file, opts, function(){
if (program.html) {
var md = fs.readFileSync(output).toString();
fs.writeFileSync(output.replace('md', 'html'), ghm.parse(md));
}
});
});
\ No newline at end of file
<? docfiles.forEach(function(doc) { ?>
<? doc.javadoc.forEach(function(comment) { ?>
<? if (comment.name && comment.private !== true) { -?>
<? if (!comment.ignore) { ?>
<a name="<?= comment.name ?>" />
<? if (comment.isConstructor) { ?>
### `new <?= comment.name ?><?= comment.paramStr ?>`
<? } else if (comment.isMixin) { ?>
### Mixin <?= comment.name ?>
<? } else if (comment.isClass) { ?>
### Class <?= comment.name ?>
<? } else { ?>
#### `<?= comment.name ?><?= comment.paramStr ?>` <? if (comment.returnTags.length > 0) { ?> -> `<?= comment.returnTags[0].joinedTypes ?>` <? } ?>
<? } ?>
<? } ?>
<?= comment.description ?>
<? if (comment.mixes.length) { ?>
### Mixes:
<? comment.mixes.forEach(function (mix) { -?>
* [<?=mix.text ?>](<?=mix.link ?>)
<? }) ?>
<? } ?>
<? if (comment.isClass) { ?>
### Members:
<? doc.members.forEach(function (member) { -?>
* [<?= member.text ?>](<?= member.link ?>)
<? }) -?>
<? } ?>
<? if (comment.deprecated) { ?>
**Deprecated** <?- comment.deprecated ?>
<? } ?>
<? if (comment.version) { ?>
Version: <?= comment.version ?>
<? } ?>
<? if (comment.see.length) { ?>See:<? } ?>
<? comment.see.forEach(function (see) { -?>
<? if (see.url !== false) { -?>
* [<?= see.text ?>](<?= see.url ?>)
<? } else { -?>
* <?= see.text ?>
<? } -?>
<? }) -?>
<? if (comment.paramTags.length > 0) { ?>
##### Params:
| Name | Type | Description |
| ---- | ---- | ----------- |
<? comment.paramTags.forEach(function(paramTag) { -?>
| <?= paramTag.name ?> | <?= paramTag.joinedTypes ?> | <?= paramTag.description ?> |
<? }) ?>
<? } ?>
<? if (comment.returnTags.length > 0 && comment.returnTags[0].description.length > 0) { ?>
__Returns:__ <?= comment.returnTags[0].description ?>
<? } ?>
<? if (comment.aliases) { ?>
__Aliases:__ *<?= comment.aliases ?>*
<? } ?>
<? if (comment.since) { ?>
__Since:__ *<?= comment.since.string ?>*
<? } ?>
======
<? } ?>
<? }) ?>
_This document is automatically generated based on source code comments. Please do not edit it directly, as your changes will be ignored. Please write on <a href="irc://irc.freenode.net/#sequelizejs">IRC</a>, open an issue or a create a pull request if you feel something can be improved. For help on how to write source code documentation see [JSDoc](http://usejsdoc.org) and [markdox](https://github.com/cbou/markdox)_
_This documentation was automagically created on <?= new Date().toString() ?>_
<? }) ?>
\ No newline at end of file
...@@ -276,26 +276,163 @@ Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc); ...@@ -276,26 +276,163 @@ Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc);
Object.defineProperty(DECIMAL, 'PRECISION', decimalDesc); Object.defineProperty(DECIMAL, 'PRECISION', decimalDesc);
Object.defineProperty(DECIMAL, 'SCALE', decimalDesc); Object.defineProperty(DECIMAL, 'SCALE', decimalDesc);
/**
* A convenience class holding commonly used data types. The datatypes are used when definining a new model using `Sequelize.define`, like this:
* ```js
* sequelize.define('model', {
* column: DataTypes.INTEGER
* })
* ```
* When defining a model you can just as easily pass a string as type, but often using the types defined here is beneficial. For example, using `DataTypes.BLOB`, mean
* that that column will be returned as an instance of `Buffer` when being fetched by sequelize.
*
* Some data types have special properties that can be accessed in order to change the data type. For example, to get an unsigned integer with zerofill you can do `DataTypes.INTEGER.UNSIGNED.ZEROFILL`.
* The order you access the properties in do not matter, so `DataTypes.INTEGER.ZEROFILL.UNSIGNED` is fine as well. The available properties are listed under each data type.
*
* To provide a length for the data type, you can invoke it like a function: `INTEGER(2)`
*
* Three of the values provided here (`NOW`, `UUIDV1` and `UUIDV4`) are special default values, that should not be used to define types. Instead they are used as shorthands for
* defining default values. For example, to get a uuid field with a default value generated following v1 of the UUID standard:
* ```js
* sequelize.define('model', {
* uuid: {
* type: DataTypes.UUID,
* defaultValue: DataTypes.UUIDV1,
* primaryKey: true
* }
* })
* ```
*
* @class DataTypes
*/
module.exports = { module.exports = {
/**
* A variable length string. Default length 255
*
* Available properties: `BINARY`
*
* @property STRING
*/
STRING: STRING, STRING: STRING,
/**
* A fixed length string. Default length 255
*
* Available properties: `BINARY`
*
* @property CHAR
*/
CHAR: CHAR, CHAR: CHAR,
/**
* An unlimited length text column
* @property TEXT
*/
TEXT: 'TEXT', TEXT: 'TEXT',
/**
* A 32 bit integer.
*
* Available properties: `UNSIGNED`, `ZEROFILL`
*
* @property INTEGER
*/
INTEGER: INTEGER, INTEGER: INTEGER,
/**
* A 64 bit integer.
*
* Available properties: `UNSIGNED`, `ZEROFILL`
*
* @property BIGINT
*/
BIGINT: BIGINT, BIGINT: BIGINT,
/**
* A datetime column
* @property DATE
*/
DATE: 'DATETIME', DATE: 'DATETIME',
/**
* A boolean / tinyint column, depending on dialect
* @property BOOLEAN
*/
BOOLEAN: 'TINYINT(1)', BOOLEAN: 'TINYINT(1)',
/**
* Floating point number. Accepts one or two arguments for precision
*
* Available properties: `UNSIGNED`, `ZEROFILL`
*
* @property FLOAT
*/
FLOAT: FLOAT, FLOAT: FLOAT,
/**
* A default value of the current timestamp
* @property NOW
*/
NOW: 'NOW', NOW: 'NOW',
/**
* Binary storage. Available lengths: `tiny`, `medium`, `long`
*
* @property BLOB
*/
BLOB: BLOB, BLOB: BLOB,
/**
* Decimal number. Accepts one or two arguments for precision
*
* Available properties: `UNSIGNED`, `ZEROFILL`
*
* @property DECIMAL
*/
DECIMAL: DECIMAL, DECIMAL: DECIMAL,
/**
* A column storing a unique univeral identifier. Use with `UUIDV1` or `UUIDV4` for default values.
* @property UUID
*/
UUID: 'UUID', UUID: 'UUID',
/**
* A default unique universal identifier generated following the UUID v1 standard
* @property UUIDV1
*/
UUIDV1: 'UUIDV1', UUIDV1: 'UUIDV1',
/**
* A default unique universal identifier generated following the UUID v2 standard
* @property UUIDV4
*/
UUIDV4: 'UUIDV4', UUIDV4: 'UUIDV4',
/**
* A virtual value that is not stored in the DB. This could for example be useful if you want to provide a default value in your model
* that is returned to the user but not stored in the DB.
*
* You could also use it to validate a value before permuting and storing it. Checking password length before hashing it for example:
* ```js
* sequelize.define('user', {
* password_hash: DataTypes.STRING
* password: {
* type: DataTypes.VIRTUAL,
* set: function (val) {
* this.setDataValue('password', val);
* this.setDataValue('password_hash', this.salt + val);
* },
* validate: {
* isLongEnough: function (val) {
* if (val.length < 7) {
* throw new Error("Please choose a longer password")
* }
* }
* }
* }
* })
* ```
* In the above code the password is stored plainly in the password field so it can be validated, but is never stored in the DB.
* @property VIRTUAL
* @alias NONE
*/
VIRTUAL: VIRTUAL, VIRTUAL: VIRTUAL,
NONE: VIRTUAL, NONE: VIRTUAL,
/**
* An enumeration. `DataTypes.ENUM('value', 'another value')`.
*
* @property ENUM
*/
get ENUM() { get ENUM() {
var result = function() { var result = function() {
return { return {
...@@ -312,8 +449,16 @@ module.exports = { ...@@ -312,8 +449,16 @@ module.exports = {
return result; return result;
}, },
/**
* An array of `type`, e.g. `DataTypes.ARRAY(DataTypes.DECIMAL)`. Only available in postgres.
* @property ARRAY
*/
ARRAY: function(type) { return type + '[]'; }, ARRAY: function(type) { return type + '[]'; },
/**
* A key / value column. Only available in postgres.
* @property HSTORE
*/
get HSTORE() { get HSTORE() {
var result = function() { var result = function() {
return { return {
......
...@@ -550,7 +550,7 @@ module.exports = (function() { ...@@ -550,7 +550,7 @@ module.exports = (function() {
values = {}; values = {};
options.fields.forEach(function(attr) { options.fields.forEach(function(attr) {
if (self.dataValues[attr] !== undefined) { if (self.dataValues[attr] !== undefined && !self.Model._isVirtualAttribute(attr)) {
values[attr] = self.dataValues[attr]; values[attr] = self.dataValues[attr];
} }
......
...@@ -256,19 +256,22 @@ module.exports = (function() { ...@@ -256,19 +256,22 @@ module.exports = (function() {
this._booleanAttributes = []; this._booleanAttributes = [];
this._dateAttributes = []; this._dateAttributes = [];
this._hstoreAttributes = []; this._hstoreAttributes = [];
this._virtualAttributes = [];
this._defaultValues = {}; this._defaultValues = {};
this.Instance.prototype.validators = {}; this.Instance.prototype.validators = {};
Utils._.each(this.rawAttributes, function(definition, name) { Utils._.each(this.rawAttributes, function(definition, name) {
if (((definition === DataTypes.BOOLEAN) || (definition.type === DataTypes.BOOLEAN))) { var type = definition.originalType || definition.type || definition;
if (type === DataTypes.BOOLEAN) {
self._booleanAttributes.push(name); self._booleanAttributes.push(name);
} } else if (type === DataTypes.DATE) {
if (((definition === DataTypes.DATE) || (definition.type === DataTypes.DATE) || (definition.originalType === DataTypes.DATE))) {
self._dateAttributes.push(name); self._dateAttributes.push(name);
} } else if (type === DataTypes.HSTORE.type) {
if (((definition === DataTypes.HSTORE.type) || (definition.type === DataTypes.HSTORE.type) || (definition.originalType === DataTypes.HSTORE.type))) {
self._hstoreAttributes.push(name); self._hstoreAttributes.push(name);
} else if (type === DataTypes.VIRTUAL) {
self._virtualAttributes.push(name);
} }
if (definition.hasOwnProperty('defaultValue')) { if (definition.hasOwnProperty('defaultValue')) {
self._defaultValues[name] = Utils._.partial( self._defaultValues[name] = Utils._.partial(
Utils.toDefaultValue, definition.defaultValue); Utils.toDefaultValue, definition.defaultValue);
...@@ -294,10 +297,17 @@ module.exports = (function() { ...@@ -294,10 +297,17 @@ module.exports = (function() {
return self._hstoreAttributes.indexOf(key) !== -1; return self._hstoreAttributes.indexOf(key) !== -1;
}); });
this._hasVirtualAttributes = !!this._virtualAttributes.length;
this._isVirtualAttribute = Utils._.memoize(function(key) {
return self._virtualAttributes.indexOf(key) !== -1;
});
this.Instance.prototype.Model = this; this.Instance.prototype.Model = this;
this._hasDefaultValues = !Utils._.isEmpty(this._defaultValues); this._hasDefaultValues = !Utils._.isEmpty(this._defaultValues);
this.tableAttributes = Utils._.omit(this.rawAttributes, this._virtualAttributes);
return this; return this;
}; };
...@@ -360,6 +370,7 @@ module.exports = (function() { ...@@ -360,6 +370,7 @@ module.exports = (function() {
}); });
this.attributes = this.rawAttributes; this.attributes = this.rawAttributes;
this.tableAttributes = Utils._.omit(this.rawAttributes, this._virtualAttributes);
this.Instance.prototype._hasCustomGetters = Object.keys(this.Instance.prototype._customGetters).length; this.Instance.prototype._hasCustomGetters = Object.keys(this.Instance.prototype._customGetters).length;
this.Instance.prototype._hasCustomSetters = Object.keys(this.Instance.prototype._customSetters).length; this.Instance.prototype._hasCustomSetters = Object.keys(this.Instance.prototype._customSetters).length;
...@@ -380,19 +391,17 @@ module.exports = (function() { ...@@ -380,19 +391,17 @@ module.exports = (function() {
*/ */
Model.prototype.sync = function(options) { Model.prototype.sync = function(options) {
options = Utils._.extend({}, this.options, options || {}); options = Utils._.extend({}, this.options, options || {});
var self = this var self = this
, doQuery = function() { , attributes = this.tableAttributes;
return self.QueryInterface.createTable(self.getTableName(), self.attributes, options);
};
if (options.force) { return Promise.resolve().then(function () {
return self.drop(options).then(function() { if (options.force) {
return doQuery().return(self); return self.drop(options);
}); }
} else { }).then(function () {
return doQuery().return(this); return self.QueryInterface.createTable(self.getTableName(), attributes, options);
} }).return(this);
}; };
/** /**
...@@ -669,7 +678,7 @@ module.exports = (function() { ...@@ -669,7 +678,7 @@ module.exports = (function() {
} }
if (options.attributes === undefined) { if (options.attributes === undefined) {
options.attributes = Object.keys(this.rawAttributes); options.attributes = Object.keys(this.tableAttributes);
} }
mapFieldNames.call(this, options, this); mapFieldNames.call(this, options, this);
...@@ -772,7 +781,7 @@ module.exports = (function() { ...@@ -772,7 +781,7 @@ module.exports = (function() {
} }
if (options.attributes === undefined) { if (options.attributes === undefined) {
options.attributes = Object.keys(this.rawAttributes); options.attributes = Object.keys(this.tableAttributes);
} }
if (options.limit === undefined && !(options.where && options.where[this.primaryKeyAttribute])) { if (options.limit === undefined && !(options.where && options.where[this.primaryKeyAttribute])) {
...@@ -1134,7 +1143,7 @@ module.exports = (function() { ...@@ -1134,7 +1143,7 @@ module.exports = (function() {
if (fieldsOrOptions instanceof Array) { if (fieldsOrOptions instanceof Array) {
options.fields = fieldsOrOptions; options.fields = fieldsOrOptions;
} else { } else {
options.fields = options.fields || Object.keys(this.attributes); options.fields = options.fields || Object.keys(this.tableAttributes);
options = Utils._.extend(options, fieldsOrOptions); options = Utils._.extend(options, fieldsOrOptions);
} }
...@@ -1209,7 +1218,7 @@ module.exports = (function() { ...@@ -1209,7 +1218,7 @@ module.exports = (function() {
// Create all in one query // Create all in one query
// Recreate records from daos to represent any changes made in hooks or validation // Recreate records from daos to represent any changes made in hooks or validation
records = daos.map(function(dao) { records = daos.map(function(dao) {
return dao.dataValues; return Utils._.omit(dao.dataValues, self._virtualAttributes);
}); });
// Map field names // Map field names
...@@ -1226,7 +1235,7 @@ module.exports = (function() { ...@@ -1226,7 +1235,7 @@ module.exports = (function() {
// Map attributes for serial identification // Map attributes for serial identification
var attributes = {}; var attributes = {};
for (var attr in self.rawAttributes) { for (var attr in self.tableAttributes) {
attributes[attr] = self.rawAttributes[attr]; attributes[attr] = self.rawAttributes[attr];
if (self.rawAttributes[attr].field) { if (self.rawAttributes[attr].field) {
attributes[self.rawAttributes[attr].field] = self.rawAttributes[attr]; attributes[self.rawAttributes[attr].field] = self.rawAttributes[attr];
...@@ -1359,7 +1368,7 @@ module.exports = (function() { ...@@ -1359,7 +1368,7 @@ module.exports = (function() {
return build.hookValidate({skip: skippedFields}).then(function(attributes) { return build.hookValidate({skip: skippedFields}).then(function(attributes) {
if (attributes && attributes.dataValues) { if (attributes && attributes.dataValues) {
attrValueHash = Utils._.pick.apply(Utils._, [].concat(attributes.dataValues).concat(Object.keys(attrValueHash))); attrValueHash = Utils._.pick(attributes.dataValues, Object.keys(attrValueHash));
} }
}); });
} }
...@@ -1374,9 +1383,9 @@ module.exports = (function() { ...@@ -1374,9 +1383,9 @@ module.exports = (function() {
}).then(function() { }).then(function() {
attrValueHashUse = attrValueHash; attrValueHashUse = attrValueHash;
// Get daos and run beforeDestroy hook on each record individually // Get daos and run beforeUpdate hook on each record individually
if (options.individualHooks) { if (options.individualHooks) {
return self.all({where: where}, {transaction: options.transaction}).then(function(_daos) { return self.all({where: where}, {transaction: options.transaction}).then(function(_daos) {
daos = _daos; daos = _daos;
if (!daos.length) { if (!daos.length) {
return []; return [];
...@@ -1441,7 +1450,7 @@ module.exports = (function() { ...@@ -1441,7 +1450,7 @@ module.exports = (function() {
} }
// Run query to update all rows // Run query to update all rows
return self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHashUse, where, options, self.rawAttributes).then(function(affectedRows) { return self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHashUse, where, options, self.tableAttributes).then(function(affectedRows) {
return [affectedRows]; return [affectedRows];
}); });
}).tap(function(result) { }).tap(function(result) {
...@@ -1731,7 +1740,7 @@ module.exports = (function() { ...@@ -1731,7 +1740,7 @@ module.exports = (function() {
} }
}); });
} else { } else {
include.attributes = Object.keys(include.model.attributes); include.attributes = Object.keys(include.model.tableAttributes);
} }
include = mapFieldNames(include, include.model); include = mapFieldNames(include, include.model);
......
...@@ -287,7 +287,7 @@ module.exports = (function() { ...@@ -287,7 +287,7 @@ module.exports = (function() {
} }
var sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); var sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter);
return this.sequelize.query(sql, null, { raw: true }).then(function(data) { return this.sequelize.query(sql, null, { raw: true }).then(function(data) {
// If no data is returned from the query, then the table name may be wrong. // If no data is returned from the query, then the table name may be wrong.
// Query generators that use information_schema for retrieving table info will just return an empty result set, // Query generators that use information_schema for retrieving table info will just return an empty result set,
......
"use strict";
/* jshint expr:true */ /* jshint expr:true */
var chai = require('chai') var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Sequelize = require(__dirname + '/../index') , Sequelize = require(__dirname + '/../index')
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
, config = require(__dirname + '/config/config') , config = require(__dirname + '/config/config');
chai.config.includeStack = true chai.config.includeStack = true;
describe(Support.getTestDialectTeaser("DaoValidator"), function() { describe(Support.getTestDialectTeaser("DaoValidator"), function() {
describe('validations', function() { describe('validations', function() {
...@@ -166,77 +168,77 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -166,77 +168,77 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
fail: "401288888888188f", fail: "401288888888188f",
pass: "4012888888881881" pass: "4012888888881881"
} }
} };
var applyFailTest = function applyFailTest(validatorDetails, i, validator) { var applyFailTest = function applyFailTest(validatorDetails, i, validator) {
var failingValue = validatorDetails.fail[i] var failingValue = validatorDetails.fail[i];
it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function(done) { it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function(done) {
var validations = {} var validations = {}
, message = validator + "(" + failingValue + ")" , message = validator + "(" + failingValue + ")";
if (validatorDetails.hasOwnProperty('spec')) { if (validatorDetails.hasOwnProperty('spec')) {
validations[validator] = validatorDetails.spec validations[validator] = validatorDetails.spec;
} else { } else {
validations[validator] = {} validations[validator] = {};
} }
validations[validator].msg = message validations[validator].msg = message;
var UserFail = this.sequelize.define('User' + config.rand(), { var UserFail = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: validations validate: validations
} }
}) });
var failingUser = UserFail.build({ name : failingValue }) var failingUser = UserFail.build({ name : failingValue });
failingUser.validate().done( function(err, _errors) { failingUser.validate().done( function(err, _errors) {
expect(_errors).not.to.be.null expect(_errors).not.to.be.null;
expect(_errors).to.be.an.instanceOf(Error) expect(_errors).to.be.an.instanceOf(Error);
expect(_errors.name[0].message).to.equal(message) expect(_errors.name[0].message).to.equal(message);
done() done();
}) });
}) });
} }
, applyPassTest = function applyPassTest(validatorDetails, j, validator) { , applyPassTest = function applyPassTest(validatorDetails, j, validator) {
var succeedingValue = validatorDetails.pass[j] var succeedingValue = validatorDetails.pass[j];
it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function(done) { it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function(done) {
var validations = {} var validations = {};
if (validatorDetails.hasOwnProperty('spec')) { if (validatorDetails.hasOwnProperty('spec')) {
validations[validator] = validatorDetails.spec validations[validator] = validatorDetails.spec;
} else { } else {
validations[validator] = {} validations[validator] = {};
} }
validations[validator].msg = validator + "(" + succeedingValue + ")" validations[validator].msg = validator + "(" + succeedingValue + ")";
var UserSuccess = this.sequelize.define('User' + config.rand(), { var UserSuccess = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: validations validate: validations
} }
}) });
var successfulUser = UserSuccess.build({ name: succeedingValue }) var successfulUser = UserSuccess.build({ name: succeedingValue });
successfulUser.validate().success(function(errors) { successfulUser.validate().success(function(errors) {
expect(errors).to.be.undefined expect(errors).to.be.undefined;
done() done();
}).error(function(err) { }).error(function(err) {
expect(err).to.deep.equal({}) expect(err).to.deep.equal({});
done() done();
}) });
}) });
} };
for (var validator in checks) { for (var validator in checks) {
if (checks.hasOwnProperty(validator)) { if (checks.hasOwnProperty(validator)) {
validator = validator.replace(/\$$/, '') validator = validator.replace(/\$$/, '');
var validatorDetails = checks[validator] var validatorDetails = checks[validator];
if (!validatorDetails.hasOwnProperty("raw")) { if (!validatorDetails.hasOwnProperty("raw")) {
validatorDetails.fail = Array.isArray(validatorDetails.fail) ? validatorDetails.fail : [ validatorDetails.fail ] validatorDetails.fail = Array.isArray(validatorDetails.fail) ? validatorDetails.fail : [ validatorDetails.fail ];
validatorDetails.pass = Array.isArray(validatorDetails.pass) ? validatorDetails.pass : [ validatorDetails.pass ] validatorDetails.pass = Array.isArray(validatorDetails.pass) ? validatorDetails.pass : [ validatorDetails.pass ];
} }
for (var i = 0; i < validatorDetails.fail.length; i++) { for (var i = 0; i < validatorDetails.fail.length; i++) {
...@@ -322,7 +324,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -322,7 +324,7 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
}) })
}) })
}) })
}) });
describe('#create', function() { describe('#create', function() {
describe('generic', function() { describe('generic', function() {
...@@ -834,6 +836,43 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() { ...@@ -834,6 +836,43 @@ describe(Support.getTestDialectTeaser("DaoValidator"), function() {
}) })
}) })
it('validates VIRTUAL fields', function () {
var User = this.sequelize.define('user', {
password_hash: Sequelize.STRING,
salt: Sequelize.STRING,
password: {
type: Sequelize.VIRTUAL,
set: function (val) {
this.setDataValue('password', val);
this.setDataValue('password_hash', this.salt + val);
},
validate: {
isLongEnough: function (val) {
if (val.length < 7) {
throw new Error("Please choose a longer password")
}
}
}
}
})
return Sequelize.Promise.all([
User.build({
password: 'short',
salt: '42'
}).validate().then(function (errors) {
expect(errors).not.to.be.undefined
expect(errors.password[0].message).to.equal('Please choose a longer password')
}),
User.build({
password: 'loooooooong',
salt: '42'
}).validate().then(function (errors) {
expect(errors).to.be.undefined
})
]);
})
it('allows me to add custom validation functions to validator.js', function () { it('allows me to add custom validation functions to validator.js', function () {
this.sequelize.Validator.extend('isExactly7Characters', function (val) { this.sequelize.Validator.extend('isExactly7Characters', function (val) {
return val.length === 7 return val.length === 7
......
"use strict";
/* jshint camelcase: false */ /* jshint camelcase: false */
/* jshint expr: true */ /* jshint expr: true */
var chai = require('chai') var chai = require('chai')
...@@ -282,8 +284,105 @@ describe(Support.getTestDialectTeaser("Model"), function () { ...@@ -282,8 +284,105 @@ describe(Support.getTestDialectTeaser("Model"), function () {
describe('types', function () { describe('types', function () {
describe('VIRTUAL', function () { describe('VIRTUAL', function () {
it('should be ignored in create, updateAttributes and find'); beforeEach(function () {
it('should be ignored in bulkCreate and findAll'); this.User = this.sequelize.define('user', {
storage: Sequelize.STRING,
field1: {
type: Sequelize.VIRTUAL,
set: function (val) {
this.setDataValue('storage', val);
this.setDataValue('field1', val);
},
get: function () {
return this.getDataValue('field1');
}
},
field2: {
type: Sequelize.VIRTUAL,
get: function () {
return 42;
}
},
virtualWithDefault: {
type: Sequelize.VIRTUAL,
defaultValue: 'cake'
}
}, { timestamps: false });
this.Task = this.sequelize.define('task', {});
this.Project = this.sequelize.define('project', {});
this.Task.belongsTo(this.User);
this.Project.hasMany(this.User);
this.User.hasMany(this.Project);
this.sqlAssert = function(sql) {
expect(sql.indexOf('field1')).to.equal(-1);
expect(sql.indexOf('field2')).to.equal(-1);
};
return this.sequelize.sync({ force: true });
});
it('should be ignored in dataValues get', function () {
var user = this.User.build({
field1: 'field1_value',
field2: 'field2_value'
});
expect(user.get()).to.deep.equal({ storage: 'field1_value', field1: 'field1_value', virtualWithDefault: 'cake', field2: 42, id: null });
});
it('should be ignored in table creation', function () {
return this.sequelize.getQueryInterface().describeTable(this.User.tableName).then(function (fields) {
expect(Object.keys(fields).length).to.equal(2);
});
});
it('should be ignored in find, findAll and includes', function () {
return Promise.all([
this.User.find().on('sql', this.sqlAssert),
this.User.findAll().on('sql', this.sqlAssert),
this.Task.findAll({
include: [
this.User
]
}).on('sql', this.sqlAssert),
this.Project.findAll({
include: [
this.User
]
}).on('sql', this.sqlAssert)
]);
});
it('should be ignored in create and updateAttributes', function () {
return this.User.create({
field1: 'something'
}).then(function (user) {
// We already verified that the virtual is not added to the table definition, so if this succeeds, were good
expect(user.virtualWithDefault).to.equal('cake');
expect(user.storage).to.equal('something');
return user.updateAttributes({
field1: 'something else'
});
}).then(function (user) {
expect(user.virtualWithDefault).to.equal('cake');
expect(user.storage).to.equal('something else');
});
});
it('should be ignored in bulkCreate and and bulkUpdate', function () {
var self = this;
return this.User.bulkCreate([{
field1: 'something'
}]).on('sql', this.sqlAssert).then(function () {
return self.User.findAll();
}).then(function (users) {
expect(users[0].storage).to.equal('something');
});
});
}); });
}); });
}); });
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!