Last active
May 15, 2020 22:13
-
-
Save taxilian/86c99fb839172089c9223f806fa4c8e1 to your computer and use it in GitHub Desktop.
Mongodb 4.2 update pipeline creator which updates modified field if anything changed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import mongoose, { LeanDocument } from "mongoose"; | |
import '@/lib/PromiseAll'; | |
// Used because Object.keys doesn't give us types for field names | |
export function keys<T extends object>(obj: T) : (keyof T)[] { | |
return Object.keys(obj) as (keyof T)[]; | |
} | |
// Spelled incorrectly on purpose | |
const defaultIgnoreType = '__UNMODIFEID__'; | |
export function makeUpdatePipeline<T extends mongoose.Document<any>>(updFields: Partial<LeanDocument<T>>, modFieldName = 'modified', ignoreType = defaultIgnoreType) { | |
// Maps the input document values; anything that is new remains unchanged | |
// and anything which is the same as the document we're matching against | |
// gets changed to the value of _ignoreType_. | |
// Note that using `k` and `v` for key names on this object is important | |
// so that $arrayToObject works in stage 2 | |
const updateArray = keys(updFields).map(fieldName => ({ | |
k: fieldName, | |
v: { $cond: { | |
if: {$eq: [`\$${fieldName}`, updFields[fieldName]]}, | |
then: ignoreType, | |
else: updFields[fieldName] | |
} } | |
})); | |
const pipeline = [ | |
// Stage 1: Filter the update fields | |
// we split the document putting the original document at `_root` and making a new | |
// _update which is updateArray but we filter out anything with a value | |
// of `ignoreType` (which we calculated when we built updateArray) | |
// from what is on the object; the $cond in the `updateArray` does that | |
{ $project: { | |
_id: 0, | |
_update: {$filter: { | |
input: updateArray, | |
as: "curItem", | |
cond: {$ne: <any>["$$curItem.v", ignoreType]} | |
}}, | |
_root: "$$ROOT", | |
}}, | |
// Stage 2: Update the document | |
// We merge three objects to create the new output document: | |
{ $replaceRoot: { | |
newRoot: { $mergeObjects: [ | |
"$_root", // Object 1: the original document | |
{$arrayToObject: "$_update"}, // Object 2: our filtered updateArray converted to an object | |
{$cond: { // Object 3: the modified field *if* updateArray isn't empty | |
if: {$gt: [ {$reduce: { input: "$_update", initialValue: 0, in: {$sum: 1}}}, 0]}, | |
then: {[modFieldName]: "$$NOW"}, | |
else: {} | |
}}, | |
]}, | |
} }, | |
]; | |
return pipeline; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment