Skip to content

Commit

Permalink
fix: findOneAndUpdate population, blacklisting, etc
Browse files Browse the repository at this point in the history
  • Loading branch information
boycce committed Apr 16, 2022
1 parent ad6146d commit 19c4fc9
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 204 deletions.
52 changes: 32 additions & 20 deletions lib/model-crud.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@ module.exports = {
}
try {
opts = await this._queryObject(opts, 'insert')
let custom = ['blacklist', 'data', 'insert', 'model', 'respond', 'skipValidation', 'validateUndefined']

// Validate
let data = await this.validate(opts.data || {}, { ...opts })
let data = await this.validate(opts.data || {}, opts) // was { ...opts }

// Insert
await util.runSeries(this.beforeInsert.map(f => f.bind(opts, data)))
let response = await this._insert(data, util.omit(opts, custom))
let response = await this._insert(data, util.omit(opts, this._queryOptions))
await util.runSeries(this.afterInsert.map(f => f.bind(opts, response)))

// Success/error
Expand Down Expand Up @@ -64,9 +63,8 @@ module.exports = {
throw new Error(`The callback passed to ${this.name}.find() is not a function`)
}
try {
opts = await this._queryObject(opts, 'find', one)
let custom = ['blacklist', 'model', 'one', 'populate', 'project', 'query', 'respond']
let lookups = []
opts = await this._queryObject(opts, 'find', one)

// Get projection
if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
Expand All @@ -80,7 +78,7 @@ module.exports = {

// Wanting to populate?
if (!opts.populate) {
var response = await this[`_find${opts.one? 'One' : ''}`](opts.query, util.omit(opts, custom))
var response = await this[`_find${opts.one? 'One' : ''}`](opts.query, util.omit(opts, this._queryOptions))
} else {
loop: for (let item of opts.populate) {
let path = util.isObject(item)? item.as : item
Expand Down Expand Up @@ -157,18 +155,14 @@ module.exports = {
* Find and update document(s) with monk, also auto populates
* @param {object} opts
* @param {array|string|false} <opts.blacklist> - augment findBL/updateBL, `false` will remove all blacklisting
* @param {array|string|false} <opts.blacklistFind> - augment findBL, `false` will remove all blacklisting
* @param {array} <opts.populate> - find population, see docs
* @param {array|string} <opts.project> - return only these fields, ignores blacklisting
* @param {array|string} <opts.projectFind> - return only these fields, ignores blacklisting
* @param {object} <opts.query> - mongodb query object
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
* @param {any} <any mongodb option>
*
* Update options:
* @param {object|array} opts.data - mongodb document update object(s)
* @param {array|string|false} <opts.blacklistUpdate> - augment updateBL, `false` will remove all blacklisting
* @param {array|string} <opts.projectUpdate> - return only these fields, ignores blacklisting
* @param {array|string|true} <opts.skipValidation> - skip validation for this field name(s)
* @param {boolean} <opts.timestamps> - whether `updatedAt` is automatically updated
* @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
Expand Down Expand Up @@ -228,37 +222,49 @@ module.exports = {
let data = opts.data
let response = null
let operators = util.pick(opts, [/^\$/])
let custom = [
'blacklist', 'data', 'model', 'one', 'populate', 'project', 'query', 'respond', 'skipValidation',
'validateUndefined'
]

// Validate
if (util.isDefined(data)) data = await this.validate(opts.data, { ...opts })
if (util.isDefined(data)) {
data = await this.validate(opts.data, opts) // was {...opts}
}
if (!util.isDefined(data) && util.isEmpty(operators)) {
throw new Error(`Please pass an update operator to ${this.name}.${type}(), e.g. data, $unset, etc`)
}
if (util.isDefined(data) && (!data || util.isEmpty(data))) {
throw new Error(`No valid data passed to ${this.name}.${type}({ data: .. })`)
}

// Hook: beforeUpdate (has access to original, non-validated opts.data)
await util.runSeries(this.beforeUpdate.map(f => f.bind(opts, data||{})))

if (data && operators['$set']) {
this.warn(`'$set' fields take precedence over the data fields for \`${this.name}.${type}()\``)
}
if (data || operators['$set']) {
operators['$set'] = { ...data, ...(operators['$set'] || {}) }
}
// Just peform a normal update if we need to populate a findOneAndUpdate
if (opts.populate && type == 'findOneAndUpdate') type ='update'

// findOneAndUpdate, get 'find' projection
if (type == 'findOneAndUpdate') {
if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
else opts.projection = this._getProjectionFromBlacklist('find', opts.blacklist)
// Just peform a normal update if we need to populate a findOneAndUpdate
if (opts.populate) type = 'update'
}

// Update
let update = await this['_' + type](opts.query, operators, util.omit(opts, custom))
let update = await this['_' + type](opts.query, operators, util.omit(opts, this._queryOptions))
if (type == 'findOneAndUpdate') response = update
else if (update.n) response = Object.assign(Object.create({ _output: update }), operators['$set']||{})

// Hook: afterUpdate (doesn't have access to validated data)
if (response) await util.runSeries(this.afterUpdate.map(f => f.bind(opts, response)))

// Hook: afterFind if findOneAndUpdate
if (response && type == 'findOneAndUpdate') {
response = await this._processAfterFind(response, opts.projection, opts)
}

// Success
if (cb) cb(null, response)
else if (opts.req && opts.respond) opts.req.res.json(response)
Expand Down Expand Up @@ -288,12 +294,11 @@ module.exports = {
}
try {
opts = await this._queryObject(opts, 'remove')
let custom = ['model', 'query', 'respond']
if (util.isEmpty(opts.query)) throw new Error('Please specify opts.query')

// Remove
await util.runSeries(this.beforeRemove.map(f => f.bind(opts)))
let response = await this._remove(opts.query, util.omit(opts, custom))
let response = await this._remove(opts.query, util.omit(opts, this._queryOptions))
await util.runSeries(this.afterRemove.map(f => f.bind(response)))

// Success
Expand Down Expand Up @@ -626,4 +631,11 @@ module.exports = {
}, this)
},

_queryOptions: [
// todo: remove type properties
'blacklist', 'data', 'find', 'findOneAndUpdate', 'insert', 'model', 'one', 'populate', 'project',
'projectionValidate', 'query', 'remove', 'req', 'respond', 'skipValidation', 'type', 'update',
'validateUndefined',
],

}
6 changes: 3 additions & 3 deletions lib/model-validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ module.exports = {
opts.skipValidation = opts.skipValidation === true ? true : util.toArray(opts.skipValidation||[])

// Get projection
if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
else opts.projection = this._getProjectionFromBlacklist(opts.update ? 'update' : 'insert', opts.blacklist)
if (opts.project) opts.projectionValidate = this._getProjectionFromProject(opts.project)
else opts.projectionValidate = this._getProjectionFromBlacklist(opts.update ? 'update' : 'insert', opts.blacklist)

// Hook: beforeValidate
await util.runSeries(this.beforeValidate.map(f => f.bind(opts, data)))
Expand Down Expand Up @@ -132,7 +132,7 @@ module.exports = {
}

// Ignore blacklisted
if (this._pathBlacklisted(path3, opts.projection) && !schema.defaultOverride) return
if (this._pathBlacklisted(path3, opts.projectionValidate) && !schema.defaultOverride) return
// Ignore insert only
if (opts.update && schema.insertOnly) return
// Ignore virtual fields
Expand Down
Loading

0 comments on commit 19c4fc9

Please sign in to comment.