Merge branch 'clarkorz-featrue/support-connector-findOrCreate'

This commit is contained in:
Raymond Feng 2015-02-11 20:50:49 -08:00
commit 96c39509c9
3 changed files with 117 additions and 28 deletions

View File

@ -418,6 +418,7 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data
* @param {Function} cb Callback called with (err, instance, created) * @param {Function} cb Callback called with (err, instance, created)
*/ */
DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) { DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
assert(arguments.length >= 2, 'At least two arguments are required'); assert(arguments.length >= 2, 'At least two arguments are required');
if (options === undefined && cb === undefined) { if (options === undefined && cb === undefined) {
@ -447,13 +448,85 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
assert(typeof cb === 'function', 'The cb argument must be a function'); assert(typeof cb === 'function', 'The cb argument must be a function');
var Model = this; var Model = this;
Model.findOne(query, function (err, record) { var self = this;
if (err) return cb(err);
if (record) return cb(null, record, false); function _findOrCreate(query, data) {
Model.create(data, options, function (err, record) { var modelName = self.modelName;
cb(err, record, record != null); data = removeUndefined(data);
self.getDataSource().connector.findOrCreate(modelName, query,
self._forDB(data),
function(err, data, created) {
var obj, Model = self.lookupModel(data);
if (data) {
obj = new Model(data, {fields: query.fields, applySetters: false,
persisted: true});
}
if (created) {
Model.notifyObserversOf('after save', { Model: Model, instance: obj },
function(err) {
cb(err, obj, created);
if (!err) Model.emit('changed', obj);
});
} else {
cb(err, obj, created);
}
});
}
if (this.getDataSource().connector.findOrCreate) {
query.limit = 1;
try {
this._normalize(query);
} catch (err) {
return process.nextTick(function () {
cb(err);
});
}
this.applyScope(query);
Model.notifyObserversOf('access', { Model: Model, query: query },
function (err, ctx) {
if (err) return cb(err);
var query = ctx.query;
var enforced = {};
var Model = self.lookupModel(data);
var obj = data instanceof Model ? data : new Model(data);
Model.applyProperties(enforced, obj);
obj.setAttributes(enforced);
Model.notifyObserversOf('before save', { Model: Model, instance: obj },
function(err, ctx) {
if (err) return cb(err);
var obj = ctx.instance;
var data = obj.toObject(true);
// validation required
obj.isValid(function (valid) {
if (valid) {
_findOrCreate(query, data);
} else {
cb(new ValidationError(obj), obj);
}
}, data);
});
}); });
}); } else {
Model.findOne(query, options, function (err, record) {
if (err) return cb(err);
if (record) return cb(null, record, false);
Model.create(data, options, function (err, record) {
cb(err, record, record != null);
});
});
}
}; };
/** /**

View File

@ -399,16 +399,29 @@ describe('Memory connector', function() {
}); });
}); });
}); });
});
require('./persistence-hooks.suite')( describe('Optimized connector', function() {
new DataSource({ connector: Memory }), var ds = new DataSource({ connector: Memory });
should);
// optimized methods
ds.connector.findOrCreate = function (model, query, data, callback) {
this.all(model, query, function (err, list) {
if (err || (list && list[0])) return callback(err, list && list[0], false);
this.create(model, data, function (err) {
callback(err, data, true);
});
}.bind(this));
};
require('./persistence-hooks.suite')(ds, should);
}); });
describe('Unoptimized connector', function() { describe('Unoptimized connector', function() {
var ds = new DataSource({ connector: Memory }); var ds = new DataSource({ connector: Memory });
// disable optimized methods // disable optimized methods
ds.connector.updateOrCreate = false; ds.connector.updateOrCreate = false;
ds.connector.findOrCreate = false;
require('./persistence-hooks.suite')(ds, should); require('./persistence-hooks.suite')(ds, should);
}); });

View File

@ -274,26 +274,25 @@ module.exports = function(dataSource, should) {
}); });
}); });
// TODO(bajtos) Enable this test for all connectors that if (dataSource.connector.findOrCreate) {
// provide optimized implementation of findOrCreate. it('triggers `before save` hook when found', function(done) {
// The unoptimized implementation does not trigger the hook TestModel.observe('before save', pushContextAndNext());
// when an existing model was found.
it.skip('triggers `before save` hook when found', function(done) {
TestModel.observe('before save', pushContextAndNext());
TestModel.findOrCreate( TestModel.findOrCreate(
{ where: { name: existingInstance.name } }, { where: { name: existingInstance.name } },
{ name: existingInstance.name }, { name: existingInstance.name },
function(err, record, created) { function(err, record, created) {
if (err) return done(err); if (err) return done(err);
observedContexts.should.eql(aTestModelCtx({ instance: { record.id.should.eql(existingInstance.id);
id: record.id, observedContexts.should.eql(aTestModelCtx({ instance: {
name: existingInstance.name, id: getLastGeneratedUid(),
extra: undefined name: existingInstance.name,
}})); extra: undefined
done(); }}));
}); done();
}); });
});
}
it('triggers `before save` hook when not found', function(done) { it('triggers `before save` hook when not found', function(done) {
TestModel.observe('before save', pushContextAndNext()); TestModel.observe('before save', pushContextAndNext());
@ -1250,6 +1249,10 @@ module.exports = function(dataSource, should) {
lastId += 1; lastId += 1;
return '' + lastId; return '' + lastId;
} }
function getLastGeneratedUid() {
return '' + lastId;
}
}); });
function deepCloneToObject(obj) { function deepCloneToObject(obj) {