Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions lib/findorcreate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
'use strict'

// I know about the deprecation of defer as outlined here:
// https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible
// but the proper way as done in https://github.com/Automattic/mongoose/blob/67c465aac5c864c3004d11d49934605037c8f520/lib/query.js#L2226
// would involve more changes to the code. As a first attempt, less changes is better I think. The proper way would
// yield the same results, so it's safe to refactor later.
function Deferred() {
/* A method to resolve the associated Promise with the value passed.
* If the promise is already settled it does nothing.
*
* @param {anything} value : This value is used to resolve the promise
* If the value is a Promise then the associated promise assumes the state
* of Promise passed as value.
*/
this.resolve = null;

/* A method to reject the associated Promise with the value passed.
* If the promise is already settled it does nothing.
*
* @param {anything} reason: The reason for the rejection of the Promise.
* Generally its an Error object. If however a Promise is passed, then the Promise
* itself will be the reason for rejection no matter the state of the Promise.
*/
this.reject = null;

/* A newly created Promise object.
* Initially in pending state.
*/
this.promise = new Promise(function(resolve, reject) {
this.resolve = resolve;
this.reject = reject;
}.bind(this));
Object.freeze(this);
}

const defaultOptions = {

/*
Expand Down Expand Up @@ -56,6 +90,25 @@ module.exports = function(schema, modelOptions) {

const options = Object.assign({}, defaultOptions, modelOptions, contextOptions)

var deferred;
if (!callback) {
deferred = new Deferred()
callback = function(err, result, wasUpdated, isNew) {
if (err) {
deferred.reject(err)
} else {
if (options.status) {
result = {
result: result,
wasUpdated: wasUpdated,
isNew: isNew
}
}
deferred.resolve(result)
}
}
}

this.findOne(query).exec((err, result) => {
if (err) return callback(err, result, false, false)
if (result && !additionalFields) return callback(err, result, false, false)
Expand All @@ -78,5 +131,7 @@ module.exports = function(schema, modelOptions) {

doc.save(options.saveOptions, err => callback(err, doc, true, !creating))
})

return deferred ? deferred.promise : undefined
}
}
14 changes: 14 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ describe('#findOrCreate()', () => {

})

it('creates a new record when just the query is provided, using promises', done => {

Fruit.findOrCreate({ name: 'Pear', color: 'green' }, null, { status: true }).then((result) => {
expect(result.result.name).to.equal('Pear')
expect(result.result.color).to.equal('green')

expect(result.isNew).to.be.true
expect(result.wasUpdated).to.be.true

done()
})

})

it('avoids trying to set mongo query keywords as fields', done => {

Fruit.findOrCreate({ name: 'Watermelon', color: { $exists: true } }, (err, result) => {
Expand Down