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

Commit 29c9be37 by Simon Schick Committed by Sushant

fix(model): make .changed() deep aware (#10851)

1 parent 481bddd6
Showing with 42 additions and 27 deletions
......@@ -104,7 +104,7 @@ class Model {
this.dataValues = {};
this._previousDataValues = {};
this._changed = {};
this._changed = new Set();
this._modelOptions = this.constructor.options;
this._options = options || {};
......@@ -160,7 +160,6 @@ class Model {
delete defaults[this.constructor._timestampAttributes.deletedAt];
}
if (Object.keys(defaults).length) {
for (key in defaults) {
if (values[key] === undefined) {
this.set(key, Utils.toDefaultValue(defaults[key], this.sequelize.options.dialect), { raw: true });
......@@ -168,7 +167,6 @@ class Model {
}
}
}
}
this.set(values, options);
}
......@@ -2450,8 +2448,9 @@ class Model {
const hasPrimary = this.primaryKeyField in values || this.primaryKeyAttribute in values;
const instance = this.build(values);
const changed = Array.from(instance._changed);
if (!options.fields) {
options.fields = Object.keys(instance._changed);
options.fields = changed;
}
return Promise.try(() => {
......@@ -2460,7 +2459,7 @@ class Model {
}
}).then(() => {
// Map field names
const updatedDataValues = _.pick(instance.dataValues, Object.keys(instance._changed));
const updatedDataValues = _.pick(instance.dataValues, changed);
const insertValues = Utils.mapValueFieldNames(instance.dataValues, Object.keys(instance.rawAttributes), this);
const updateValues = Utils.mapValueFieldNames(updatedDataValues, options.fields, this);
const now = Utils.now(this.sequelize.options.dialect);
......@@ -3477,7 +3476,7 @@ class Model {
setDataValue(key, value) {
const originalValue = this._previousDataValues[key];
if (!Utils.isPrimitive(value) && value !== null || value !== originalValue) {
if (!_.isEqual(value, originalValue)) {
this.changed(key, true);
}
......@@ -3654,7 +3653,7 @@ class Model {
// custom setter should have changed value, get that changed value
// TODO: v5 make setters return new value instead of changing internal store
const newValue = this.dataValues[key];
if (!Utils.isPrimitive(newValue) && newValue !== null || newValue !== originalValue) {
if (!_.isEqual(newValue, originalValue)) {
this._previousDataValues[key] = originalValue;
this.changed(key, true);
}
......@@ -3707,7 +3706,7 @@ class Model {
// Check for data type type comparators
!(value instanceof Utils.SequelizeMethod) && this.constructor._dataTypeChanges[key] && this.constructor._dataTypeChanges[key].call(this, value, originalValue, options) ||
// Check default
!this.constructor._dataTypeChanges[key] && (!Utils.isPrimitive(value) && value !== null || value !== originalValue)
!this.constructor._dataTypeChanges[key] && !_.isEqual(value, originalValue)
)
) {
this._previousDataValues[key] = originalValue;
......@@ -3731,23 +3730,42 @@ class Model {
*
* If changed is called without an argument and no keys have changed, it will return `false`.
*
* Please note that this function will return `false` when a property from a nested (for example JSON) property
* was edited manually, you must call `changed('key', true)` manually in these cases.
* Writing an entirely new object (eg. deep cloned) will be detected.
*
* @example
* ```
* const mdl = await MyModel.findOne();
* mdl.myJsonField.a = 1;
* console.log(mdl.changed()) => false
* mdl.save(); // this will not save anything
* mdl.changed('myJsonField', true);
* console.log(mdl.changed()) => ['myJsonField']
* mdl.save(); // will save
* ```
*
* @param {string} [key] key to check or change status of
* @param {any} [value] value to set
*
* @returns {boolean|Array}
*/
changed(key, value) {
if (key) {
if (value !== undefined) {
this._changed[key] = value;
if (key === undefined) {
if (this._changed.size > 0) {
return Array.from(this._changed);
}
return false;
}
if (value === true) {
this._changed.add(key);
return this;
}
return this._changed[key] || false;
if (value === false) {
this._changed.delete(key);
return this;
}
const changed = Object.keys(this.dataValues).filter(key => this.changed(key));
return changed.length ? changed : false;
return this._changed.has(key);
}
/**
......
......@@ -78,21 +78,18 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
expect(user.changed('birthday')).to.equal(false);
});
it('should return true for changed JSON with same object', function() {
const user = this.User.build({
meta: {
city: 'Copenhagen'
}
it('should not detect changes when equal', function() {
for (const value of [null, 1, 'asdf', new Date(), [], {}, Buffer.from('')]) {
const t = new this.User({
json: value
}, {
isNewRecord: false,
raw: true
});
const meta = user.get('meta');
meta.city = 'Stockholm';
user.set('meta', meta);
expect(user.changed('meta')).to.equal(true);
t.json = value;
expect(t.changed('json')).to.be.false;
expect(t.changed()).to.be.false;
}
});
it('should return true for JSON dot.separated key with changed values', function() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!