Merge pull request #826 from strongloop/updateAttributes-refactoring

Refactor `updateAttributes`
This commit is contained in:
Amir-61 2016-01-25 10:26:44 -05:00
commit 63482ef061
1 changed files with 126 additions and 102 deletions

View File

@ -52,6 +52,48 @@ function getIdValue(m, data) {
return data && data[idName(m)]; return data && data[idName(m)];
} }
function copyData(from, to) {
for (var key in from) {
to[key] = from[key];
}
}
function convertDataPropertiesByType(data) {
var typedData = {};
for (var key in data) {
// Convert the properties by type
typedData[key] = data[key];
if (typeof typedData[key] === 'object'
&& typedData[key] !== null
&& typeof typedData[key].toObject === 'function') {
typedData[key] = typedData[key].toObject();
}
}
return typedData;
}
/**
* Apply strict check for model's data.
* Notice: Please note this method modifies `inst` when `strict` is `validate`.
*/
function applyStrictCheck(model, strict, data, inst, cb) {
var props = model.definition.properties;
var keys = Object.keys(data);
var result = {}, key;
for (var i = 0; i < keys.length; i++) {
key = keys[i];
if (props[key]) {
result[key] = data[key];
} else if (strict === 'throw') {
cb(new Error('Unknown property: ' + key));
return;
} else if (strict === 'validate') {
inst.__unknownProperties.push(key);
}
}
cb(null, result);
}
function setIdValue(m, data, value) { function setIdValue(m, data, value) {
if (data) { if (data) {
data[idName(m)] = value; data[idName(m)] = value;
@ -2374,6 +2416,7 @@ DataAccessObject.prototype.unsetAttribute = function unsetAttribute(name, nullif
* @param {Function} cb Callback function called with (err, instance) * @param {Function} cb Callback function called with (err, instance)
*/ */
DataAccessObject.prototype.updateAttributes = function updateAttributes(data, options, cb) { DataAccessObject.prototype.updateAttributes = function updateAttributes(data, options, cb) {
var self = this;
var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); var connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
if (connectionPromise) { if (connectionPromise) {
return connectionPromise; return connectionPromise;
@ -2452,126 +2495,107 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, op
data = ctx.data; data = ctx.data;
if (strict && !allowExtendedOperators) { if (strict && !allowExtendedOperators) {
var props = Model.definition.properties; applyStrictCheck(self.constructor, strict, data, inst, validateAndSave);
var keys = Object.keys(data);
var result = {}, key;
for (var i = 0; i < keys.length; i++) {
key = keys[i];
if (props[key]) {
result[key] = data[key];
} else if (strict === 'throw') {
cb(new Error('Unknown property: ' + key));
return;
} else if (strict === 'validate') {
inst.__unknownProperties.push(key);
}
}
data = removeUndefined(result);
}
var doValidate = true;
if (options.validate === undefined) {
if (Model.settings.automaticValidation !== undefined) {
doValidate = Model.settings.automaticValidation;
}
} else { } else {
doValidate = options.validate; validateAndSave(null, data);
} }
// update instance's properties function validateAndSave(err, data) {
inst.setAttributes(data); if (err) return cb(err);
data = removeUndefined(data);
if (doValidate){ var doValidate = true;
inst.isValid(function (valid) { if (options.validate === undefined) {
if (!valid) { if (Model.settings.automaticValidation !== undefined) {
cb(new ValidationError(inst), inst); doValidate = Model.settings.automaticValidation;
return;
} }
} else {
doValidate = options.validate;
}
triggerSave(); // update instance's properties
}, data); inst.setAttributes(data);
} else {
triggerSave();
}
function triggerSave(){ if (doValidate){
inst.trigger('save', function (saveDone) { inst.isValid(function (valid) {
inst.trigger('update', function (done) { if (!valid) {
var typedData = {}; cb(new ValidationError(inst), inst);
return;
for (var key in data) {
// Convert the properties by type
inst[key] = data[key];
typedData[key] = inst[key];
if (typeof typedData[key] === 'object'
&& typedData[key] !== null
&& typeof typedData[key].toObject === 'function') {
typedData[key] = typedData[key].toObject();
}
} }
context.data = typedData; triggerSave();
}, data);
} else {
triggerSave();
}
function updateAttributesCallback(err) { function triggerSave(){
if (err) return cb(err); inst.trigger('save', function (saveDone) {
var ctx = { inst.trigger('update', function (done) {
Model: Model, copyData(data, inst);
data: context.data, var typedData = convertDataPropertiesByType(data);
hookState: hookState, context.data = typedData;
options: options
}; function updateAttributesCallback(err) {
Model.notifyObserversOf('loaded', ctx, function(err) {
if (err) return cb(err); if (err) return cb(err);
var ctx = {
Model: Model,
data: context.data,
hookState: hookState,
options: options
};
Model.notifyObserversOf('loaded', ctx, function(err) {
if (err) return cb(err);
inst.__persisted = true; inst.__persisted = true;
// By default, the instance passed to updateAttributes callback is NOT updated // By default, the instance passed to updateAttributes callback is NOT updated
// with the changes made through persist/loaded hooks. To preserve // with the changes made through persist/loaded hooks. To preserve
// backwards compatibility, we introduced a new setting updateOnLoad, // backwards compatibility, we introduced a new setting updateOnLoad,
// which if set, will apply these changes to the model instance too. // which if set, will apply these changes to the model instance too.
if(Model.settings.updateOnLoad) { if(Model.settings.updateOnLoad) {
inst.setAttributes(ctx.data); inst.setAttributes(ctx.data);
} }
done.call(inst, function () { done.call(inst, function () {
saveDone.call(inst, function () { saveDone.call(inst, function () {
if (err) return cb(err, inst); if (err) return cb(err, inst);
var context = { var context = {
Model: Model, Model: Model,
instance: inst, instance: inst,
isNewInstance: false, isNewInstance: false,
hookState: hookState, hookState: hookState,
options: options options: options
}; };
Model.notifyObserversOf('after save', context, function(err) { Model.notifyObserversOf('after save', context, function(err) {
if(!err) Model.emit('changed', inst); if(!err) Model.emit('changed', inst);
cb(err, inst); cb(err, inst);
});
}); });
}); });
}); });
});
}
var ctx = {
Model: Model,
where: byIdQuery(Model, getIdValue(Model, inst)).where,
data: context.data,
currentInstance: inst,
isNewInstance: false,
hookState: hookState,
options: options
};
Model.notifyObserversOf('persist', ctx, function(err) {
if (connector.updateAttributes.length === 5) {
connector.updateAttributes(model, getIdValue(inst.constructor, inst),
inst.constructor._forDB(context.data), options, updateAttributesCallback);
} else {
connector.updateAttributes(model, getIdValue(inst.constructor, inst),
inst.constructor._forDB(context.data), updateAttributesCallback);
} }
});
var ctx = {
Model: Model,
where: byIdQuery(Model, getIdValue(Model, inst)).where,
data: context.data,
currentInstance: inst,
isNewInstance: false,
hookState: hookState,
options: options
};
Model.notifyObserversOf('persist', ctx, function(err) {
if (connector.updateAttributes.length === 5) {
connector.updateAttributes(model, getIdValue(inst.constructor, inst),
inst.constructor._forDB(context.data), options, updateAttributesCallback);
} else {
connector.updateAttributes(model, getIdValue(inst.constructor, inst),
inst.constructor._forDB(context.data), updateAttributesCallback);
}
});
}, data, cb);
}, data, cb); }, data, cb);
}, data, cb); }
} }
}); });
return cb.promise; return cb.promise;