Created
April 22, 2014 12:10
-
-
Save optilude/11176381 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
syncModels: function(model, collection, where, include, keep) { | |
var collectionItems = {}, // lookup: id -> item from client | |
toUpdate = {}, // lookup: id -> db model | |
toCreate = [], // models | |
toDelete = []; // models | |
include = include || []; | |
// keep track of models we are updating | |
collection.forEach(function(item) { | |
if(item.id) { | |
collectionItems[item.id] = item; | |
} | |
}); | |
// helper function to recursively sync children | |
function syncRecursive(obj, item) { | |
if(!obj.Model.associations) | |
return null; | |
return Promise.all(_.map(obj.Model.associations, function(association) { | |
// XXX: Other types not yet implemented | |
if(association.associationType != "HasMany") { | |
return null; | |
} | |
var target = association.target, | |
key = normalizeCase(association.options.as? association.options.as : target.tableName), | |
val = item[key], | |
where = {}; | |
if(val === undefined) { | |
return null; | |
} | |
where[association.identifier] = obj.id; | |
return module.exports.utils.syncModels(target, val, where, [], keep); | |
})); | |
} | |
return model.findAll({ | |
where: where, | |
include: include | |
}) | |
.then(function(response) { | |
// Keep track of which models we'll need to create, update and delete | |
response.forEach(function(item) { | |
var matchingModel = collectionItems[item.id]; | |
if(matchingModel === undefined) { | |
if(!keep) { | |
toDelete.push(item); | |
} | |
} else { | |
toUpdate[item.id] = item; | |
} | |
}); | |
toCreate = collection.filter(function(item) { | |
return !item.id || toUpdate[item.id] === undefined; | |
}); | |
// Execute database operations | |
return Promise.join( | |
// Create | |
Promise.all(toCreate.map(function(item) { | |
return model.create(_.pick(_.extend({}, item, where), _.keys(model.rawAttributes))) | |
.then(function(obj) { | |
return syncRecursive(obj, item); | |
}); | |
})), | |
// Update | |
Promise.all(_.values(toUpdate).map(function(obj) { | |
var item = collectionItems[obj.id], | |
newAttributes = _.pick(_.extend(_.omit(item, 'id', 'createdAt', 'updatedAt'), where), _.keys(model.rawAttributes)); | |
return obj.updateAttributes(newAttributes) | |
.then(function(obj) { | |
return syncRecursive(obj, item); | |
}); | |
})), | |
// Delete | |
Promise.all(toDelete.map(function(item) { | |
return item.destroy(); | |
})) | |
); | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
syncModels: function(model, collection, where, include, keep) { | |
var syncDeferred = promise.defer(), // finishing the sync | |
chainDeferred = promise.defer(), // inserting/updating/deleting model instances | |
assocDeferred = {}, // syncing child relations | |
clientModels = {}, // id -> client model | |
updateModels = []; // id -> db model | |
include = include || []; | |
function normalizeCase(string) { | |
return string.charAt(0).toLowerCase() + string.slice(1); | |
} | |
// The next two methods are used to help us make sure child objects | |
// in one-to-many assocaitions are saved. When setting up the | |
// operation to add or update a model, we call `deferAssociations()` | |
// to create a deferred promise of those things having been | |
// inserted/updated. When models have been successfully inserted or | |
// updated, we call `syncAssociations()` to actually do the work | |
// and resolve the promise. | |
function deferAssociations(id, model) { | |
if(!model.associations) | |
return; | |
_.each(model.associations, function(association) { | |
// XXX: Other types not yet implemented | |
if(association.associationType != "HasMany") | |
return; | |
var target = association.target, | |
key = normalizeCase(association.options.as? association.options.as : target.tableName); | |
assocDeferred[id + ":" + key] = promise.defer(); | |
}); | |
} | |
function syncAssociations(id, obj, item) { | |
if(!obj.Model.associations) | |
return; | |
_.each(obj.Model.associations, function(association) { | |
// XXX: Other types not yet implemented | |
if(association.associationType != "HasMany") | |
return; | |
var target = association.target, | |
key = normalizeCase(association.options.as? association.options.as : target.tableName), | |
val = item[key], | |
where = {}, | |
adef = assocDeferred[id + ":" + key]; | |
if(val !== undefined) { | |
where[association.identifier] = obj.id; | |
module.exports.utils.syncModels(target, val, where, [], keep) | |
.then( | |
function() { | |
adef.resolve(); | |
}, | |
function(error) { | |
adef.reject(error); | |
} | |
); | |
} | |
}); | |
} | |
// keep track of models we are updating | |
_.each(collection, function(item) { | |
if(item.id) { | |
clientModels[item.id] = item; | |
} | |
}); | |
model.findAll({ | |
where: where, | |
include: include | |
}) | |
.success(function(response) { | |
var chainer = new Sequelize.Utils.QueryChainer(); | |
_.each(response, function(item) { | |
var matchingModel = clientModels[item.id]; | |
if(matchingModel === undefined) { | |
// not found in the request? destroy it | |
if(!keep) { | |
chainer.add(item.destroy()); | |
} | |
} else { | |
// got one already? we'll need to add it | |
updateModels[item.id] = item; | |
} | |
}); | |
// add new models | |
_.each(collection, function(item, index) { | |
if(!item.id || updateModels[item.id] === undefined) { | |
var id = "new:" + index; | |
deferAssociations(id, model); | |
chainer.add( | |
model.create(_.pick(_.extend({}, item, where), _.keys(model.rawAttributes))) | |
.success(function (obj) { | |
syncAssociations(id, obj, item); | |
}) | |
); | |
} | |
}); | |
// update existing ones | |
_.each(updateModels, function(obj) { | |
var id = obj.id, | |
clientModel = clientModels[id], | |
newAttributes = _.pick(_.extend(_.omit(clientModel, 'id', 'createdAt', 'updatedAt'), where), _.keys(model.rawAttributes)); | |
deferAssociations(id, model); | |
chainer.add( | |
obj.updateAttributes(newAttributes) | |
.success(function(obj) { | |
syncAssociations(id, obj, clientModel); | |
}) | |
); | |
}); | |
chainer.run() | |
.success(function() { | |
chainDeferred.resolve("success"); | |
}) | |
.error(function(errors) { | |
console.error(errors); | |
chainDeferred.reject("database error updating records"); | |
}); | |
}).error(function () { | |
chainDeferred.reject("database error querying for records"); | |
}); | |
chainDeferred | |
.then( | |
function() { | |
// When the chainer is done, we know we'll have at least | |
// *created* all the deferreds for any child associations | |
promise.allOrNone(_.values(assocDeferred)) | |
.then( | |
function() { | |
syncDeferred.resolve(); | |
}, | |
function(error) { | |
syncDeferred.reject(error); | |
} | |
); | |
}, | |
function(error) { | |
syncDeferred.reject(error); | |
} | |
); | |
return syncDeferred.promise; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment