Change#getModel(), Doc cleanup, Conflict event
This commit is contained in:
parent
4bab42478f
commit
1e2ad9fba9
|
@ -66,6 +66,7 @@ Change.Conflict = Conflict;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.setup = function() {
|
Change.setup = function() {
|
||||||
|
DataModel.setup.call(this);
|
||||||
var Change = this;
|
var Change = this;
|
||||||
|
|
||||||
Change.getter.id = function() {
|
Change.getter.id = function() {
|
||||||
|
@ -271,7 +272,9 @@ Change.prototype.getModelCtor = function() {
|
||||||
|
|
||||||
Change.prototype.equals = function(change) {
|
Change.prototype.equals = function(change) {
|
||||||
if(!change) return false;
|
if(!change) return false;
|
||||||
return change.rev === this.rev;
|
var thisRev = this.rev || null;
|
||||||
|
var thatRev = change.rev || null;
|
||||||
|
return thisRev === thatRev;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -402,56 +405,169 @@ Change.handleError = function(err) {
|
||||||
|
|
||||||
Change.prototype.getModelId = function() {
|
Change.prototype.getModelId = function() {
|
||||||
// TODO(ritch) get rid of the need to create an instance
|
// TODO(ritch) get rid of the need to create an instance
|
||||||
var Model = this.constructor.settings.model;
|
var Model = this.constructor.settings.trackModel;
|
||||||
var id = this.modelId;
|
var id = this.modelId;
|
||||||
var m = new Model();
|
var m = new Model();
|
||||||
m.setId(id);
|
m.setId(id);
|
||||||
return m.getId();
|
return m.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Change.prototype.getModel = function(callback) {
|
||||||
|
var Model = this.constructor.settings.trackModel;
|
||||||
|
var id = this.getModelId();
|
||||||
|
Model.findById(id, callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When two changes conflict a conflict is created.
|
* When two changes conflict a conflict is created.
|
||||||
*
|
*
|
||||||
* **Note: call `conflict.fetch()` to get the `target` and `source` models.
|
* **Note: call `conflict.fetch()` to get the `target` and `source` models.
|
||||||
*
|
*
|
||||||
* @param {Change} sourceChange The change object for the source model
|
* @param {*} sourceModelId
|
||||||
* @param {Change} targetChange The conflicting model's change object
|
* @param {*} targetModelId
|
||||||
* @property {Model} source The source model instance
|
* @property {ModelClass} source The source model instance
|
||||||
* @property {Model} target The target model instance
|
* @property {ModelClass} target The target model instance
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Conflict(sourceChange, targetChange) {
|
function Conflict(modelId, SourceModel, TargetModel) {
|
||||||
this.sourceChange = sourceChange;
|
this.SourceModel = SourceModel;
|
||||||
this.targetChange = targetChange;
|
this.TargetModel = TargetModel;
|
||||||
|
this.SourceChange = SourceModel.getChangeModel();
|
||||||
|
this.TargetChange = TargetModel.getChangeModel();
|
||||||
|
this.modelId = modelId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Conflict.prototype.fetch = function(cb) {
|
/**
|
||||||
|
* Fetch the conflicting models.
|
||||||
|
*
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error}
|
||||||
|
* @param {DataModel} source
|
||||||
|
* @param {DataModel} target
|
||||||
|
*/
|
||||||
|
|
||||||
|
Conflict.prototype.models = function(cb) {
|
||||||
var conflict = this;
|
var conflict = this;
|
||||||
var tasks = [
|
var SourceModel = this.SourceModel;
|
||||||
|
var TargetModel = this.TargetModel;
|
||||||
|
var source;
|
||||||
|
var target;
|
||||||
|
|
||||||
|
async.parallel([
|
||||||
getSourceModel,
|
getSourceModel,
|
||||||
getTargetModel
|
getTargetModel
|
||||||
];
|
], done);
|
||||||
|
|
||||||
async.parallel(tasks, cb);
|
function getSourceModel(cb) {
|
||||||
|
SourceModel.findById(conflict.modelId, function(err, model) {
|
||||||
function getSourceModel(change, cb) {
|
|
||||||
conflict.sourceModel.getModel(function(err, model) {
|
|
||||||
if(err) return cb(err);
|
if(err) return cb(err);
|
||||||
conflict.source = model;
|
source = model;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTargetModel(cb) {
|
function getTargetModel(cb) {
|
||||||
conflict.targetModel.getModel(function(err, model) {
|
TargetModel.findById(conflict.modelId, function(err, model) {
|
||||||
if(err) return cb(err);
|
if(err) return cb(err);
|
||||||
conflict.target = model;
|
target = model;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function done(err) {
|
||||||
|
if(err) return cb(err);
|
||||||
|
cb(null, source, target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Conflict.prototype.resolve = function(cb) {
|
/**
|
||||||
this.sourceChange.prev = this.targetChange.rev;
|
* Get the conflicting changes.
|
||||||
this.sourceChange.save(cb);
|
*
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {Change} sourceChange
|
||||||
|
* @param {Change} targetChange
|
||||||
|
*/
|
||||||
|
|
||||||
|
Conflict.prototype.changes = function(cb) {
|
||||||
|
var conflict = this;
|
||||||
|
var sourceChange;
|
||||||
|
var targetChange;
|
||||||
|
|
||||||
|
async.parallel([
|
||||||
|
getSourceChange,
|
||||||
|
getTargetChange
|
||||||
|
], done);
|
||||||
|
|
||||||
|
function getSourceChange(cb) {
|
||||||
|
conflict.SourceChange.findOne({
|
||||||
|
modelId: conflict.sourceModelId
|
||||||
|
}, function(err, change) {
|
||||||
|
if(err) return cb(err);
|
||||||
|
sourceChange = change;
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTargetChange(cb) {
|
||||||
|
debugger;
|
||||||
|
conflict.TargetChange.findOne({
|
||||||
|
modelId: conflict.targetModelId
|
||||||
|
}, function(err, change) {
|
||||||
|
if(err) return cb(err);
|
||||||
|
targetChange = change;
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function done(err) {
|
||||||
|
if(err) return cb(err);
|
||||||
|
cb(null, sourceChange, targetChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the conflict.
|
||||||
|
*
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
*/
|
||||||
|
|
||||||
|
Conflict.prototype.resolve = function(cb) {
|
||||||
|
var conflict = this;
|
||||||
|
conflict.changes(function(err, sourceChange, targetChange) {
|
||||||
|
if(err) return callback(err);
|
||||||
|
sourceChange.prev = targetChange.rev;
|
||||||
|
sourceChange.save(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the conflict type.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* // possible results are
|
||||||
|
* Change.UPDATE // => source and target models were updated
|
||||||
|
* Change.DELETE // => the source and or target model was deleted
|
||||||
|
* Change.UNKNOWN // => the conflict type is uknown or due to an error
|
||||||
|
* ```
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {String} type The conflict type.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Conflict.prototype.type = function(cb) {
|
||||||
|
var conflict = this;
|
||||||
|
this.changes(function(err, sourceChange, targetChange) {
|
||||||
|
if(err) return cb(err);
|
||||||
|
var sourceChangeType = sourceChange.type();
|
||||||
|
var targetChangeType = targetChange.type();
|
||||||
|
if(sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) {
|
||||||
|
return cb(null, Change.UPDATE);
|
||||||
|
}
|
||||||
|
if(sourceChangeType === Change.DELETE || targetChangeType === Change.DELETE) {
|
||||||
|
return cb(null, Change.DELETE);
|
||||||
|
}
|
||||||
|
return cb(null, Change.UNKNOWN);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,6 @@ DataModel.setup = function setupDataModel() {
|
||||||
return val ? new DataModel(val) : val;
|
return val ? new DataModel(val) : val;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// enable change tracking (usually for replication)
|
// enable change tracking (usually for replication)
|
||||||
if(this.settings.trackChanges) {
|
if(this.settings.trackChanges) {
|
||||||
DataModel._defineChangeModel();
|
DataModel._defineChangeModel();
|
||||||
|
@ -424,6 +423,8 @@ DataModel.setupRemoting = function() {
|
||||||
var typeName = DataModel.modelName;
|
var typeName = DataModel.modelName;
|
||||||
var options = DataModel.settings;
|
var options = DataModel.settings;
|
||||||
|
|
||||||
|
// TODO(ritch) setRemoting should create its own function...
|
||||||
|
|
||||||
setRemoting(DataModel.create, {
|
setRemoting(DataModel.create, {
|
||||||
description: 'Create a new instance of the model and persist it into the data source',
|
description: 'Create a new instance of the model and persist it into the data source',
|
||||||
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
|
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
|
||||||
|
@ -592,6 +593,11 @@ DataModel.changes = function(since, filter, callback) {
|
||||||
callback = since;
|
callback = since;
|
||||||
since = -1;
|
since = -1;
|
||||||
}
|
}
|
||||||
|
if(typeof filter === 'function') {
|
||||||
|
callback = filter;
|
||||||
|
since = -1;
|
||||||
|
filter = {};
|
||||||
|
}
|
||||||
|
|
||||||
var idName = this.dataSource.idName(this.modelName);
|
var idName = this.dataSource.idName(this.modelName);
|
||||||
var Change = this.getChangeModel();
|
var Change = this.getChangeModel();
|
||||||
|
@ -699,28 +705,16 @@ DataModel.replicate = function(since, targetModel, options, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var tasks = [
|
var tasks = [
|
||||||
getLocalChanges,
|
getSourceChanges,
|
||||||
getDiffFromTarget,
|
getDiffFromTarget,
|
||||||
createSourceUpdates,
|
createSourceUpdates,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
checkpoint
|
checkpoint
|
||||||
];
|
];
|
||||||
|
|
||||||
async.waterfall(tasks, function(err) {
|
async.waterfall(tasks, done);
|
||||||
if(err) return callback(err);
|
|
||||||
var conflicts = diff.conflicts.map(function(change) {
|
|
||||||
var sourceChange = new Change({
|
|
||||||
modelName: sourceModel.modelName,
|
|
||||||
modelId: change.modelId
|
|
||||||
});
|
|
||||||
var targetChange = new TargetChange(change);
|
|
||||||
return new Change.Conflict(sourceChange, targetChange);
|
|
||||||
});
|
|
||||||
|
|
||||||
callback && callback(null, conflicts);
|
function getSourceChanges(cb) {
|
||||||
});
|
|
||||||
|
|
||||||
function getLocalChanges(cb) {
|
|
||||||
sourceModel.changes(since, options.filter, cb);
|
sourceModel.changes(since, options.filter, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -735,7 +729,7 @@ DataModel.replicate = function(since, targetModel, options, callback) {
|
||||||
sourceModel.createUpdates(diff.deltas, cb);
|
sourceModel.createUpdates(diff.deltas, cb);
|
||||||
} else {
|
} else {
|
||||||
// nothing to replicate
|
// nothing to replicate
|
||||||
callback(null, []);
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -747,6 +741,22 @@ DataModel.replicate = function(since, targetModel, options, callback) {
|
||||||
var cb = arguments[arguments.length - 1];
|
var cb = arguments[arguments.length - 1];
|
||||||
sourceModel.checkpoint(cb);
|
sourceModel.checkpoint(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function done(err) {
|
||||||
|
if(err) return callback(err);
|
||||||
|
|
||||||
|
var conflicts = diff.conflicts.map(function(change) {
|
||||||
|
return new Change.Conflict(
|
||||||
|
change.modelId, sourceModel, targetModel
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(conflicts.length) {
|
||||||
|
sourceModel.emit('conflicts', conflicts);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback && callback(null, conflicts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -924,7 +934,7 @@ DataModel._defineChangeModel = function() {
|
||||||
return this.Change = BaseChangeModel.extend(this.modelName + '-change',
|
return this.Change = BaseChangeModel.extend(this.modelName + '-change',
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
model: this
|
trackModel: this
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue