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

Commit 222fb13c by Mick Hansen

[feat] instance.update() will now also update values provided in beforeUpdate ho…

…oks if fields was not set by user
1 parent 23a1b086
...@@ -161,6 +161,18 @@ Hooks.addHook = function(hookType, name, fn) { ...@@ -161,6 +161,18 @@ Hooks.addHook = function(hookType, name, fn) {
return this; return this;
}; };
/*
* Check whether the mode has any hooks of this type
*
* @param {String} hookType
*
* @alias hasHooks
*/
Hooks.hasHook = function(hookType) {
return !!this.options.hooks[hookType].length;
};
Hooks.hasHooks = Hooks.hasHook;
/** /**
* A hook that is run before validation * A hook that is run before validation
* @param {String} name * @param {String} name
......
...@@ -470,6 +470,8 @@ module.exports = (function() { ...@@ -470,6 +470,8 @@ module.exports = (function() {
} else { } else {
options.fields = _.intersection(this.changed(), Object.keys(this.Model.attributes)); options.fields = _.intersection(this.changed(), Object.keys(this.Model.attributes));
} }
options.defaultFields = options.fields;
} }
if (options.returning === undefined) { if (options.returning === undefined) {
...@@ -501,11 +503,11 @@ module.exports = (function() { ...@@ -501,11 +503,11 @@ module.exports = (function() {
options.fields.push(createdAtAttr); options.fields.push(createdAtAttr);
} }
return Promise.try(function() { return Promise.bind(this).then(function() {
// Validate // Validate
if (options.validate) { if (options.validate) {
options.skip = _.difference(Object.keys(self.rawAttributes), options.fields); options.skip = _.difference(Object.keys(this.Model.rawAttributes), options.fields);
return (options.hooks ? self.hookValidate(options) : self.validate(options)).then(function() { return (options.hooks ? this.hookValidate(options) : this.validate(options)).then(function() {
delete options.skip; delete options.skip;
}); });
} }
...@@ -516,12 +518,12 @@ module.exports = (function() { ...@@ -516,12 +518,12 @@ module.exports = (function() {
} }
}); });
for (var attrName in self.Model.rawAttributes) { for (var attrName in this.Model.rawAttributes) {
if (self.Model.rawAttributes.hasOwnProperty(attrName)) { if (self.Model.rawAttributes.hasOwnProperty(attrName)) {
var definition = self.Model.rawAttributes[attrName] var definition = this.Model.rawAttributes[attrName]
, isEnum = definition.type.toString() === DataTypes.ENUM.toString() , isEnum = definition.type.toString() === DataTypes.ENUM.toString()
, isMySQL = ['mysql', 'mariadb'].indexOf(self.Model.modelManager.sequelize.options.dialect) !== -1 , isMySQL = ['mysql', 'mariadb'].indexOf(this.Model.modelManager.sequelize.options.dialect) !== -1
, ciCollation = !!self.Model.options.collate && self.Model.options.collate.match(/_ci$/i) , ciCollation = !!this.Model.options.collate && this.Model.options.collate.match(/_ci$/i)
, valueOutOfScope; , valueOutOfScope;
// Unfortunately for MySQL CI collation we need to map/lowercase values again // Unfortunately for MySQL CI collation we need to map/lowercase values again
...@@ -579,31 +581,50 @@ module.exports = (function() { ...@@ -579,31 +581,50 @@ module.exports = (function() {
// Add the values to the Instance // Add the values to the Instance
self.dataValues = _.extend(self.dataValues, values); self.dataValues = _.extend(self.dataValues, values);
return Promise.try(function() { return Promise.bind(this).then(function() {
// Run before hook // Run before hook
if (options.hooks) { if (options.hooks) {
return self.Model.runHooks('before' + hook, self, options).then(function() { var hookFields
, ignoreChanged = _.difference(this.changed(), options.fields) // In case of update where it's only supposed to update the passed values and the hook values
, fieldsHadUpdatedAtAttr = updatedAtAttr && options.fields.indexOf(updatedAtAttr) !== -1;
if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) !== -1) {
ignoreChanged = _.without(ignoreChanged, updatedAtAttr);
}
return this.Model.runHooks('before' + hook, this, options).bind(this).then(function() {
if (options.defaultFields && !this.isNewRecord) {
//options.fields = _.without.apply(_, [_.intersection(this.changed(), Object.keys(this.Model.attributes))].concat(ignoreChanged));
options.fields = _.difference(_.intersection(this.changed(), Object.keys(this.Model.attributes)), ignoreChanged);
if (fieldsHadUpdatedAtAttr && options.fields.indexOf(updatedAtAttr) === -1) {
options.fields.push(updatedAtAttr);
}
hookFields = _.difference(options.fields, options.defaultFields);
}
// dataValues might have changed inside the hook, rebuild the values hash // dataValues might have changed inside the hook, rebuild the values hash
values = {}; values = {};
options.fields.forEach(function(attr) { options.fields.forEach(function(attr) {
if (self.dataValues[attr] !== undefined && !self.Model._isVirtualAttribute(attr)) { if (this.dataValues[attr] !== undefined && !this.Model._isVirtualAttribute(attr)) {
values[attr] = self.dataValues[attr]; values[attr] = this.dataValues[attr];
} }
// Field name mapping // Field name mapping
if (self.Model.rawAttributes[attr].field && self.Model.rawAttributes[attr].field !== attr) { if (this.Model.rawAttributes[attr].field && this.Model.rawAttributes[attr].field !== attr) {
values[self.Model.rawAttributes[attr].field] = values[attr]; values[this.Model.rawAttributes[attr].field] = values[attr];
delete values[attr]; delete values[attr];
} }
}); }, this);
args[2] = values; args[2] = values;
}); });
} }
}).then(function() { }).then(function() {
return self.QueryInterface[query].apply(self.QueryInterface, args) return self.QueryInterface[query].apply(self.QueryInterface, args)
.tap(function(result) { .then(function(result) {
// Transfer database generated values (defaults, autoincrement, etc) // Transfer database generated values (defaults, autoincrement, etc)
Object.keys(self.Model.rawAttributes).forEach(function (attr) { Object.keys(self.Model.rawAttributes).forEach(function (attr) {
if (self.Model.rawAttributes[attr].field && if (self.Model.rawAttributes[attr].field &&
...@@ -616,6 +637,7 @@ module.exports = (function() { ...@@ -616,6 +637,7 @@ module.exports = (function() {
values = _.extend(values, result.dataValues); values = _.extend(values, result.dataValues);
result.dataValues = _.extend(result.dataValues, values); result.dataValues = _.extend(result.dataValues, values);
return result;
}) })
.tap(function(result) { .tap(function(result) {
// Run after hook // Run after hook
...@@ -627,6 +649,7 @@ module.exports = (function() { ...@@ -627,6 +649,7 @@ module.exports = (function() {
options.fields.forEach(function (field) { options.fields.forEach(function (field) {
result._previousDataValues[field] = result.dataValues[field]; result._previousDataValues[field] = result.dataValues[field];
}); });
self.isNewRecord = false;
return result; return result;
}); });
}); });
...@@ -694,7 +717,10 @@ module.exports = (function() { ...@@ -694,7 +717,10 @@ module.exports = (function() {
this.set(values, {attributes: options.fields}); this.set(values, {attributes: options.fields});
if (!options.fields) options.fields = _.intersection(Object.keys(values), this.changed()); if (!options.fields) {
options.fields = _.intersection(Object.keys(values), this.changed());
options.defaultFields = options.fields;
}
return this.save(options); return this.save(options);
}; };
......
...@@ -98,7 +98,7 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -98,7 +98,7 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
return user.save().then(function () { return user.save().then(function () {
user.set('validateTest', 5); user.set('validateTest', 5);
expect(user.changed('validateTest')).to.be.ok; expect(user.changed('validateTest')).to.be.ok;
return user.updateAttributes({ return user.update({
validateCustom: '1' validateCustom: '1'
}); });
}).then(function () { }).then(function () {
...@@ -111,6 +111,37 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -111,6 +111,37 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
}); });
}); });
it('should update attributes added in hooks when default fields are used', function () {
var User = this.sequelize.define('User' + config.rand(), {
name: DataTypes.STRING,
bio: DataTypes.TEXT,
email: DataTypes.STRING
});
User.beforeUpdate(function(instance, options) {
instance.set('email', 'B');
});
return User.sync({force: true}).then(function() {
return User.create({
name: 'A',
bio: 'A',
email: 'A'
}).then(function (user) {
return user.update({
name: 'B',
bio: 'B'
});
}).then(function () {
return User.findOne({});
}).then(function (user) {
expect(user.get('name')).to.equal('B');
expect(user.get('bio')).to.equal('B');
expect(user.get('email')).to.equal('B');
});
});
});
it('should not set attributes that are not specified by fields', function () { it('should not set attributes that are not specified by fields', function () {
var User = this.sequelize.define('User' + config.rand(), { var User = this.sequelize.define('User' + config.rand(), {
name: DataTypes.STRING, name: DataTypes.STRING,
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!