feat: add capability for insert multiple rows in single query
Signed-off-by: Samarpan Bhattacharya <this.is.samy@gmail.com>
This commit is contained in:
parent
cb20ae6575
commit
d29bec72a8
|
@ -47,6 +47,8 @@ function Memory(m, settings) {
|
|||
|
||||
util.inherits(Memory, Connector);
|
||||
|
||||
Memory.prototype.multiInsertSupported = true;
|
||||
|
||||
Memory.prototype.getDefaultIdType = function() {
|
||||
return Number;
|
||||
};
|
||||
|
@ -277,6 +279,29 @@ Memory.prototype.create = function create(model, data, options, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
Memory.prototype.createAll = function create(model, dataArray, options, callback) {
|
||||
const returnArr = [];
|
||||
async.eachSeries(
|
||||
dataArray,
|
||||
(data, cb) => {
|
||||
this._createSync(model, data, (err, id) => {
|
||||
if (err) {
|
||||
return process.nextTick(function() {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
const returnData = Object.assign({}, data);
|
||||
this.setIdValue(model, returnData, id);
|
||||
returnArr.push(returnData);
|
||||
this.saveToFile(id, cb);
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
callback(err, returnArr);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
Memory.prototype.updateOrCreate = function(model, data, options, callback) {
|
||||
const self = this;
|
||||
this.exists(model, self.getIdValue(model, data), options, function(err, exists) {
|
||||
|
|
236
lib/dao.js
236
lib/dao.js
|
@ -457,6 +457,242 @@ DataAccessObject.create = function(data, options, cb) {
|
|||
return cb.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an instances of Model with given data array and save to the attached data source. Callback is optional.
|
||||
* Example:
|
||||
*```js
|
||||
* User.createAll([{first: 'Joe', last: 'Bob'},{first: 'Tom', last: 'Cat'}], function(err, users) {
|
||||
* console.log(users[0] instanceof User); // true
|
||||
* });
|
||||
* ```
|
||||
* Note: You must include a callback and use the created models provided in the callback if your code depends on your model being
|
||||
* saved or having an ID.
|
||||
*
|
||||
* @param {Object} [dataArray] Optional data object with array of records
|
||||
* @param {Object} [options] Options for create
|
||||
* @param {Function} [cb] Callback function called with these arguments:
|
||||
* - err (null or Error)
|
||||
* - instance (null or Models)
|
||||
*/
|
||||
DataAccessObject.createAll = function(dataArray, options, cb) {
|
||||
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
||||
if (connectionPromise) {
|
||||
return connectionPromise;
|
||||
}
|
||||
|
||||
let Model = this;
|
||||
const connector = Model.getConnector();
|
||||
if (!connector.multiInsertSupported) {
|
||||
// If multi insert is not supported, then, revert to create method
|
||||
// Array is handled in create method already in legacy code
|
||||
// This ensures backwards compatibility
|
||||
return this.create(dataArray, options, cb);
|
||||
}
|
||||
assert(
|
||||
typeof connector.createAll === 'function',
|
||||
'createAll() must be implemented by the connector',
|
||||
);
|
||||
|
||||
if (options === undefined && cb === undefined) {
|
||||
if (typeof dataArray === 'function') {
|
||||
// create(cb)
|
||||
cb = dataArray;
|
||||
dataArray = [];
|
||||
}
|
||||
} else if (cb === undefined) {
|
||||
if (typeof options === 'function') {
|
||||
// create(data, cb);
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
}
|
||||
|
||||
dataArray = dataArray || [];
|
||||
options = options || {};
|
||||
cb = cb || utils.createPromiseCallback();
|
||||
|
||||
assert(typeof dataArray === 'object' && dataArray.length,
|
||||
'The data argument must be an array with length > 0');
|
||||
assert(typeof options === 'object', 'The options argument must be an object');
|
||||
assert(typeof cb === 'function', 'The cb argument must be a function');
|
||||
|
||||
const validationPromises = [];
|
||||
for (let index = 0; index < dataArray.length; index++) {
|
||||
const data = dataArray[index];
|
||||
const hookState = {};
|
||||
|
||||
const enforced = {};
|
||||
let obj;
|
||||
|
||||
try {
|
||||
obj = new Model(data);
|
||||
|
||||
this.applyProperties(enforced, obj);
|
||||
obj.setAttributes(enforced);
|
||||
} catch (err) {
|
||||
process.nextTick(function() {
|
||||
cb(err);
|
||||
});
|
||||
return cb.promise;
|
||||
}
|
||||
|
||||
Model = this.lookupModel(data); // data-specific
|
||||
if (Model !== obj.constructor) obj = new Model(data);
|
||||
|
||||
const context = {
|
||||
Model: Model,
|
||||
instance: obj,
|
||||
isNewInstance: true,
|
||||
hookState: hookState,
|
||||
options: options,
|
||||
};
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
Model.notifyObserversOf('before save', context, function(err) {
|
||||
if (err) return reject({
|
||||
error: err,
|
||||
data: obj,
|
||||
});
|
||||
|
||||
const d = obj.toObject(true);
|
||||
|
||||
// options has precedence on model-setting
|
||||
if (options.validate === false) {
|
||||
return resolve(obj);
|
||||
}
|
||||
|
||||
// only when options.validate is not set, take model-setting into consideration
|
||||
if (
|
||||
options.validate === undefined &&
|
||||
Model.settings.automaticValidation === false
|
||||
) {
|
||||
return resolve(obj);
|
||||
}
|
||||
|
||||
// validation required
|
||||
obj.isValid(
|
||||
function(valid) {
|
||||
if (valid) {
|
||||
resolve(obj);
|
||||
} else {
|
||||
reject({
|
||||
error: new ValidationError(obj),
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
},
|
||||
d,
|
||||
options,
|
||||
);
|
||||
});
|
||||
});
|
||||
validationPromises.push(promise);
|
||||
}
|
||||
|
||||
Promise.all(validationPromises).then((objArray) => {
|
||||
const values = [];
|
||||
const valMap = new Map();
|
||||
objArray.forEach((obj) => {
|
||||
const val = Model._sanitizeData(obj.toObject(true), options);
|
||||
values.push(val);
|
||||
valMap.set(obj, applyDefaultsOnWrites(val, Model.definition));
|
||||
});
|
||||
|
||||
function createCallback(err, savedArray) {
|
||||
if (err) {
|
||||
return cb(err, objArray);
|
||||
}
|
||||
|
||||
const context = values.map((val) => {
|
||||
return {
|
||||
Model: Model,
|
||||
data: val,
|
||||
isNewInstance: true,
|
||||
hookState: {},
|
||||
options: options,
|
||||
};
|
||||
});
|
||||
Model.notifyObserversOf('loaded', context, function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
const afterSavePromises = [];
|
||||
savedArray.map((obj) => {
|
||||
const dataModel = new Model(obj);
|
||||
|
||||
let afterSavePromise;
|
||||
if (options.notify !== false) {
|
||||
const context = {
|
||||
Model: Model,
|
||||
instance: dataModel,
|
||||
isNewInstance: true,
|
||||
hookState: {},
|
||||
options: options,
|
||||
};
|
||||
|
||||
afterSavePromise = new Promise((resolve, reject) => {
|
||||
Model.notifyObserversOf('after save', context, function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(dataModel);
|
||||
}
|
||||
});
|
||||
});
|
||||
afterSavePromises.push(afterSavePromise);
|
||||
} else {
|
||||
afterSavePromises.push(Promise.resolve(dataModel));
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all(afterSavePromises).then(saved => {
|
||||
cb(null, saved);
|
||||
}).catch(err => {
|
||||
cb(err, objArray);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
context = objArray.map(obj => {
|
||||
return {
|
||||
Model: Model,
|
||||
data: valMap.get(obj),
|
||||
isNewInstance: true,
|
||||
currentInstance: obj,
|
||||
hookState: {},
|
||||
options: options,
|
||||
};
|
||||
});
|
||||
const persistPromise = new Promise((resolve, reject) => {
|
||||
Model.notifyObserversOf('persist', context, function(err, ctx) {
|
||||
if (err) return reject(err);
|
||||
|
||||
const objDataArray = ctx
|
||||
.map((obj) => {
|
||||
return obj.currentInstance.constructor._forDB(obj.data);
|
||||
})
|
||||
.filter((objData) => !!objData);
|
||||
resolve(objDataArray);
|
||||
});
|
||||
});
|
||||
persistPromise.then((objDataArray) => {
|
||||
invokeConnectorMethod(
|
||||
connector,
|
||||
'createAll',
|
||||
Model,
|
||||
[objDataArray],
|
||||
options,
|
||||
createCallback,
|
||||
);
|
||||
}).catch((err) => {
|
||||
err && cb(err);
|
||||
});
|
||||
}).catch((err) => {
|
||||
err && cb(err.error, err.data);
|
||||
});
|
||||
|
||||
return cb.promise;
|
||||
};
|
||||
|
||||
// Implementation of applyDefaultOnWrites property
|
||||
function applyDefaultsOnWrites(obj, modelDefinition) {
|
||||
for (const key in modelDefinition.properties) {
|
||||
|
|
|
@ -31,8 +31,9 @@
|
|||
"lint": "eslint .",
|
||||
"build": "npm run build-ts-types",
|
||||
"build-ts-types": "tsc -p tsconfig.json --outDir dist",
|
||||
"pretest": "npm run build",
|
||||
"test": "nyc mocha",
|
||||
"posttest": "npm run tsc && npm run lint"
|
||||
"posttest": "npm run lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.2.0",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -69,27 +69,6 @@ Object.defineProperty(module.exports, 'skip', {
|
|||
value: skip,
|
||||
});
|
||||
|
||||
function clearAndCreate(model, data, callback) {
|
||||
const createdItems = [];
|
||||
model.destroyAll(function() {
|
||||
nextItem(null, null);
|
||||
});
|
||||
|
||||
let itemIndex = 0;
|
||||
|
||||
function nextItem(err, lastItem) {
|
||||
if (lastItem !== null) {
|
||||
createdItems.push(lastItem);
|
||||
}
|
||||
if (itemIndex >= data.length) {
|
||||
callback(createdItems);
|
||||
return;
|
||||
}
|
||||
model.create(data[itemIndex], nextItem);
|
||||
itemIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable mocha/handle-done-callback */
|
||||
function testOrm(dataSource) {
|
||||
const requestsAreCounted = dataSource.name !== 'mongodb';
|
||||
|
@ -251,6 +230,45 @@ function testOrm(dataSource) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should save objects when createAll is invoked', function(test) {
|
||||
const title = 'Initial title',
|
||||
title2 = 'Hello world',
|
||||
date = new Date();
|
||||
|
||||
Post.createAll(
|
||||
[{
|
||||
title: title,
|
||||
date: date,
|
||||
}],
|
||||
function(err, objs) {
|
||||
const obj = objs[0];
|
||||
test.ok(obj.id, 'Object id should present');
|
||||
test.equals(obj.title, title);
|
||||
// test.equals(obj.date, date);
|
||||
obj.title = title2;
|
||||
test.ok(obj.propertyChanged('title'), 'Title changed');
|
||||
obj.save(function(err, obj) {
|
||||
test.equal(obj.title, title2);
|
||||
test.ok(!obj.propertyChanged('title'));
|
||||
|
||||
const p = new Post({title: 1});
|
||||
p.title = 2;
|
||||
p.save(function(err, obj) {
|
||||
test.ok(!p.propertyChanged('title'));
|
||||
p.title = 3;
|
||||
test.ok(p.propertyChanged('title'));
|
||||
test.equal(p.title_was, 2);
|
||||
p.save(function() {
|
||||
test.equal(p.title_was, 3);
|
||||
test.ok(!p.propertyChanged('title'));
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should create object with initial data', function(test) {
|
||||
const title = 'Initial title',
|
||||
date = new Date;
|
||||
|
|
|
@ -632,10 +632,13 @@ function seed(done) {
|
|||
{id: 5, seq: 5, name: 'Stuart Sutcliffe', order: 3, vip: true},
|
||||
];
|
||||
|
||||
async.series([
|
||||
async.series(
|
||||
[
|
||||
User.destroyAll.bind(User),
|
||||
function(cb) {
|
||||
async.each(beatles, User.create.bind(User), cb);
|
||||
User.createAll(beatles, cb);
|
||||
},
|
||||
], done);
|
||||
],
|
||||
done,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,8 +25,16 @@ ContextRecorder.prototype.recordAndNext = function(transformFm) {
|
|||
transformFm(context);
|
||||
}
|
||||
|
||||
if (Array.isArray(context)) {
|
||||
context = context.map(ctx => {
|
||||
const ctxCopy = deepCloneToObject(ctx);
|
||||
ctxCopy.hookState.test = true;
|
||||
return ctxCopy;
|
||||
});
|
||||
} else {
|
||||
context = deepCloneToObject(context);
|
||||
context.hookState.test = true;
|
||||
}
|
||||
|
||||
if (typeof self.records === 'string') {
|
||||
self.records = context;
|
||||
|
@ -37,7 +45,11 @@ ContextRecorder.prototype.recordAndNext = function(transformFm) {
|
|||
self.records = [self.records];
|
||||
}
|
||||
|
||||
if (Array.isArray(context)) {
|
||||
self.records.push(...context);
|
||||
} else {
|
||||
self.records.push(context);
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
|
|
@ -671,6 +671,310 @@ module.exports = function(dataSource, should, connectorCapabilities) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PersistedModel.createAll', function() {
|
||||
it('triggers hooks in the correct order', function(done) {
|
||||
monitorHookExecution();
|
||||
|
||||
TestModel.createAll(
|
||||
[{name: '1'}, {name: '2'}],
|
||||
function(err) {
|
||||
if (err) return done(err);
|
||||
|
||||
hookMonitor.names.should.eql([
|
||||
'before save',
|
||||
'before save',
|
||||
'persist',
|
||||
'loaded',
|
||||
'after save',
|
||||
'after save',
|
||||
]);
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('aborts when `after save` fires when option to notify is false', function(done) {
|
||||
monitorHookExecution();
|
||||
|
||||
TestModel.create(
|
||||
[{name: '1'}, {name: '2'}],
|
||||
{notify: false},
|
||||
function(err) {
|
||||
if (err) return done(err);
|
||||
|
||||
hookMonitor.names.should.not.containEql('after save');
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('triggers `before save` hook for each item in the array', function(done) {
|
||||
TestModel.observe('before save', ctxRecorder.recordAndNext());
|
||||
|
||||
TestModel.createAll([{name: '1'}, {name: '2'}], function(err, list) {
|
||||
if (err) return done(err);
|
||||
// Creation of multiple instances is executed in parallel
|
||||
ctxRecorder.records.sort(function(c1, c2) {
|
||||
return c1.instance.name - c2.instance.name;
|
||||
});
|
||||
ctxRecorder.records.should.eql([
|
||||
aCtxForModel(TestModel, {
|
||||
instance: {id: list[0].id, name: '1', extra: undefined},
|
||||
isNewInstance: true,
|
||||
}),
|
||||
aCtxForModel(TestModel, {
|
||||
instance: {id: list[1].id, name: '2', extra: undefined},
|
||||
isNewInstance: true,
|
||||
}),
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('aborts when `before save` hook fails', function(done) {
|
||||
TestModel.observe('before save', nextWithError(expectedError));
|
||||
|
||||
TestModel.createAll([{name: '1'}, {name: '2'}], function(err) {
|
||||
err.should.eql(expectedError);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('applies updates from `before save` hook to each item in the array', function(done) {
|
||||
TestModel.observe('before save', function(ctx, next) {
|
||||
ctx.instance.should.be.instanceOf(TestModel);
|
||||
ctx.instance.extra = 'hook data';
|
||||
next();
|
||||
});
|
||||
|
||||
TestModel.createAll(
|
||||
[{id: uid.next(), name: 'a-name'}, {id: uid.next(), name: 'b-name'}],
|
||||
function(err, instances) {
|
||||
if (err) return done(err);
|
||||
instances.forEach(instance => {
|
||||
instance.should.have.property('extra', 'hook data');
|
||||
});
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('validates model after `before save` hook', function(done) {
|
||||
TestModel.observe('before save', invalidateTestModel());
|
||||
|
||||
TestModel.createAll([{name: 'created1'}, {name: 'created2'}], function(err) {
|
||||
(err || {}).should.be.instanceOf(ValidationError);
|
||||
(err.details.codes || {}).should.eql({name: ['presence']});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers `persist` hook', function(done) {
|
||||
TestModel.observe('persist', ctxRecorder.recordAndNext());
|
||||
|
||||
TestModel.createAll(
|
||||
[{id: 'new-id-1', name: 'a name'}, {id: 'new-id-2', name: 'b name'}],
|
||||
function(err, instances) {
|
||||
if (err) return done(err);
|
||||
|
||||
ctxRecorder.records.should.eql([
|
||||
aCtxForModel(TestModel, {
|
||||
data: {id: 'new-id-1', name: 'a name'},
|
||||
isNewInstance: true,
|
||||
currentInstance: {extra: null, id: 'new-id-1', name: 'a name'},
|
||||
}),
|
||||
aCtxForModel(TestModel, {
|
||||
data: {id: 'new-id-2', name: 'b name'},
|
||||
isNewInstance: true,
|
||||
currentInstance: {extra: null, id: 'new-id-2', name: 'b name'},
|
||||
}),
|
||||
]);
|
||||
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('applies updates from `persist` hook', function(done) {
|
||||
TestModel.observe(
|
||||
'persist',
|
||||
ctxRecorder.recordAndNext(function(ctxArr) {
|
||||
// It's crucial to change `ctx.data` reference, not only data props
|
||||
ctxArr.forEach(ctx => {
|
||||
ctx.data = Object.assign({}, ctx.data, {extra: 'hook data'});
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// By default, the instance passed to create callback is NOT updated
|
||||
// with the changes made through persist/loaded hooks. To preserve
|
||||
// backwards compatibility, we introduced a new setting updateOnLoad,
|
||||
// which if set, will apply these changes to the model instance too.
|
||||
TestModel.settings.updateOnLoad = true;
|
||||
TestModel.createAll(
|
||||
[{id: 'new-id', name: 'a name'}],
|
||||
function(err, instances) {
|
||||
if (err) return done(err);
|
||||
|
||||
instances.forEach(instance => {
|
||||
instance.should.have.property('extra', 'hook data');
|
||||
});
|
||||
|
||||
// Also query the database here to verify that, on `create`
|
||||
// updates from `persist` hook are reflected into database
|
||||
TestModel.findById('new-id', function(err, dbInstance) {
|
||||
if (err) return done(err);
|
||||
should.exists(dbInstance);
|
||||
dbInstance.toObject(true).should.eql({
|
||||
id: 'new-id',
|
||||
name: 'a name',
|
||||
extra: 'hook data',
|
||||
});
|
||||
done();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('triggers `loaded` hook', function(done) {
|
||||
TestModel.observe('loaded', ctxRecorder.recordAndNext());
|
||||
|
||||
// By default, the instance passed to create callback is NOT updated
|
||||
// with the changes made through persist/loaded hooks. To preserve
|
||||
// backwards compatibility, we introduced a new setting updateOnLoad,
|
||||
// which if set, will apply these changes to the model instance too.
|
||||
TestModel.settings.updateOnLoad = true;
|
||||
TestModel.createAll(
|
||||
[
|
||||
{id: 'new-id-1', name: 'a name'},
|
||||
{id: 'new-id-2', name: 'b name'},
|
||||
],
|
||||
function(err) {
|
||||
if (err) return done(err);
|
||||
|
||||
ctxRecorder.records.sort(function(c1, c2) {
|
||||
return c1.data.name - c2.data.name;
|
||||
});
|
||||
ctxRecorder.records.should.eql([
|
||||
aCtxForModel(TestModel, {
|
||||
data: {id: 'new-id-1', name: 'a name'},
|
||||
isNewInstance: true,
|
||||
}),
|
||||
aCtxForModel(TestModel, {
|
||||
data: {id: 'new-id-2', name: 'b name'},
|
||||
isNewInstance: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('emits error when `loaded` hook fails', function(done) {
|
||||
TestModel.observe('loaded', nextWithError(expectedError));
|
||||
TestModel.createAll(
|
||||
[{id: 'new-id', name: 'a name'}],
|
||||
function(err) {
|
||||
err.should.eql(expectedError);
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('applies updates from `loaded` hook', function(done) {
|
||||
TestModel.observe(
|
||||
'loaded',
|
||||
ctxRecorder.recordAndNext(function(ctx) {
|
||||
// It's crucial to change `ctx.data` reference, not only data props
|
||||
ctx.data = Object.assign({}, ctx.data, {extra: 'hook data'});
|
||||
}),
|
||||
);
|
||||
|
||||
// By default, the instance passed to create callback is NOT updated
|
||||
// with the changes made through persist/loaded hooks. To preserve
|
||||
// backwards compatibility, we introduced a new setting updateOnLoad,
|
||||
// which if set, will apply these changes to the model instance too.
|
||||
TestModel.settings.updateOnLoad = true;
|
||||
TestModel.create(
|
||||
[{id: 'new-id', name: 'a name'}],
|
||||
function(err, instances) {
|
||||
if (err) return done(err);
|
||||
|
||||
instances.forEach((instance) => {
|
||||
instance.should.have.property('extra', 'hook data');
|
||||
});
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('triggers `after save` hook', function(done) {
|
||||
TestModel.observe('after save', ctxRecorder.recordAndNext());
|
||||
|
||||
TestModel.createAll([{name: '1'}, {name: '2'}], function(err, list) {
|
||||
if (err) return done(err);
|
||||
|
||||
ctxRecorder.records.sort(function(c1, c2) {
|
||||
return c1.instance.name - c2.instance.name;
|
||||
});
|
||||
ctxRecorder.records.should.eql([
|
||||
aCtxForModel(TestModel, {
|
||||
instance: {id: list[0].id, name: '1', extra: undefined},
|
||||
isNewInstance: true,
|
||||
}),
|
||||
aCtxForModel(TestModel, {
|
||||
instance: {id: list[1].id, name: '2', extra: undefined},
|
||||
isNewInstance: true,
|
||||
}),
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('aborts when `after save` hook fails', function(done) {
|
||||
TestModel.observe('after save', nextWithError(expectedError));
|
||||
|
||||
TestModel.createAll([{name: 'created'}], function(err) {
|
||||
err.should.eql(expectedError);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('applies updates from `after save` hook', function(done) {
|
||||
TestModel.observe('after save', function(ctx, next) {
|
||||
ctx.instance.should.be.instanceOf(TestModel);
|
||||
ctx.instance.extra = 'hook data';
|
||||
next();
|
||||
});
|
||||
|
||||
TestModel.createAll([
|
||||
{name: 'a-name'},
|
||||
{name: 'b-name'},
|
||||
], function(err, instances) {
|
||||
if (err) return done(err);
|
||||
instances.forEach((instance) => {
|
||||
instance.should.have.property('extra', 'hook data');
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('do not emit `after save` when before save fails for even one', function(done) {
|
||||
TestModel.observe('before save', function(ctx, next) {
|
||||
if (ctx.instance.name === 'fail') next(expectedError);
|
||||
else next();
|
||||
});
|
||||
|
||||
TestModel.observe('after save', ctxRecorder.recordAndNext());
|
||||
|
||||
TestModel.createAll([{name: 'ok'}, {name: 'fail'}], function(err, list) {
|
||||
err.should.eql(expectedError);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PersistedModel.findOrCreate', function() {
|
||||
it('triggers `access` hook', function(done) {
|
||||
TestModel.observe('access', ctxRecorder.recordAndNext());
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
// This file is licensed under the MIT License.
|
||||
// License text available at https://opensource.org/licenses/MIT
|
||||
|
||||
import {Callback, Options, PromiseOrVoid} from './common';
|
||||
import {ModelBase, ModelData} from './model';
|
||||
import {Filter, Where} from './query';
|
||||
import { Callback, Options, PromiseOrVoid } from './common';
|
||||
import { ModelBase, ModelData } from './model';
|
||||
import { Filter, Where } from './query';
|
||||
|
||||
/**
|
||||
* Data object for persisted models
|
||||
|
@ -47,6 +47,21 @@ export declare class PersistedModel extends ModelBase {
|
|||
callback?: Callback<PersistedModel[]>,
|
||||
): PromiseOrVoid<PersistedModel[]>;
|
||||
|
||||
/**
|
||||
* Creates an array of new instances of Model, and save to database in one DB query.
|
||||
*
|
||||
* @param {Object[]} [data] Optional data argument. An array of instances.
|
||||
*
|
||||
* @callback {Function} callback Callback function called with `cb(err, obj)` signature.
|
||||
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
|
||||
* @param {Object} models Model instances or null.
|
||||
*/
|
||||
static createAll(
|
||||
data: PersistedData[],
|
||||
options?: Options,
|
||||
callback?: Callback<PersistedModel[]>,
|
||||
): PromiseOrVoid<PersistedModel[]>;
|
||||
|
||||
/**
|
||||
* Update or insert a model instance
|
||||
* @param {Object} data The model instance data to insert.
|
||||
|
|
Loading…
Reference in New Issue