I need to iterate over all the models in a Bookshelf collection, compute some information, then store that information back in each model. It's important that I do this in a single transaction, for rollbacks on errors.
The problem I am running into is the only way I can really think of to do this is with Promise.map
(Bluebird), but a bookshelf collection can't be passed to map. For example, this does not work (Thing
is a Model, Promise
is a bluebird promise):
Bookshelf.transaction(function (t) {
return Thing.fetchAll({transacting:t}).then(function (things) {
return Promise.map(things, function (thing) {
return thing.save({
value: computeSomeValueSync(thing)
}, {
transacting: t
});
});
});
}).tap(function () {
console.log("update complete");
});
Because things
can't be passed to Promise.map
, and there doesn't seem to be anything in the Bookshelf API that can obtain an array of models from a collection...
How can I do this?
All right, I found a solution, at least.
First step is to write a function that computes and saves the value, and make it be a member of the bookshelf model. So, for the example from my post, I'd define the following function in Thing
when extending the model:
... = bookshelf.Model.extend({
...
updateSomeValue: function (options) {
return this.save({
value: computeSomeValueSync(this)
}, options);
}
});
Where options
is the options to pass to save, which we can use to pass the transaction through. Easy enough. Then, we can do the equivalent of Promise.map
with Collection#invokeThen
, like this:
Bookshelf.transaction(function (t) {
return Thing.fetchAll({transacting:t}).then(function (things) {
return things.invokeThen("updateSomeValue", {transacting:t});
});
}).tap(function () {
console.log("update complete");
});
There, invokeThen
essentially does what I intended to do with Promise.map
-- returns a promise that becomes fulfilled once all the promises returned by Thing#updateSomeValue
are fulfilled.
It's only mildly inconvenient in that I have to add the model method, but it does make a bit of sense at least. The interface is a little weird because the docs are tough to piece together. But, at least it's possible.
Still open to other ideas.