Skip to content

Commit e462d00

Browse files
authored
feat: handle push/pull, arrayFilter and updateMany operations
1 parent 3d813bf commit e462d00

File tree

4 files changed

+206
-31
lines changed

4 files changed

+206
-31
lines changed

lib/index.js

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ exports.default = function (schema, opts) {
162162
var _this2 = this;
163163

164164
this.model.findOne(this._conditions).then(function (original) {
165+
if (original) _this2._originalId = original._id;
165166
original = original || new _this2.model({});
166167
_this2._original = toJSON(original.data());
167168
}).then(function () {
@@ -185,7 +186,8 @@ exports.default = function (schema, opts) {
185186

186187
if (result.nModified === 0) return;
187188

188-
var conditions = Object.assign({}, this._conditions, this._update.$set || this._update);
189+
var conditions = void 0;
190+
if (this._originalId) conditions = { _id: { $eq: this._originalId } };else conditions = mergeQueryConditionsWithUpdate(this._conditions, this._update);
189191

190192
this.model.findOne(conditions).then(function (doc) {
191193
if (!doc) return next();
@@ -202,14 +204,37 @@ exports.default = function (schema, opts) {
202204
function preUpdateMany(next) {
203205
var _this4 = this;
204206

205-
this.model.find(this._conditions).then(function () {
206-
var originals = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
207+
this.model.find(this._conditions).then(function (originals) {
208+
var originalIds = [];
209+
var originalData = [];
210+
var _iteratorNormalCompletion = true;
211+
var _didIteratorError = false;
212+
var _iteratorError = undefined;
207213

208-
_this4._originals = [].concat(originals).map(function (original) {
209-
return original || new _this4.model({});
210-
}).map(function (original) {
211-
return toJSON(original.data());
212-
});
214+
try {
215+
for (var _iterator = originals[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
216+
var original = _step.value;
217+
218+
originalIds.push(original._id);
219+
originalData.push(toJSON(original.data()));
220+
}
221+
} catch (err) {
222+
_didIteratorError = true;
223+
_iteratorError = err;
224+
} finally {
225+
try {
226+
if (!_iteratorNormalCompletion && _iterator.return) {
227+
_iterator.return();
228+
}
229+
} finally {
230+
if (_didIteratorError) {
231+
throw _iteratorError;
232+
}
233+
}
234+
}
235+
236+
_this4._originalIds = originalIds;
237+
_this4._originals = originalData;
213238
}).then(function () {
214239
return next();
215240
}).catch(next);
@@ -220,7 +245,8 @@ exports.default = function (schema, opts) {
220245

221246
if (result.nModified === 0) return;
222247

223-
var conditions = Object.assign({}, this._conditions, this._update.$set || this._update);
248+
var conditions = void 0;
249+
if (this._originalIds.length === 0) conditions = mergeQueryConditionsWithUpdate(this._conditions, this._update);else conditions = { _id: { $in: this._originalIds } };
224250

225251
this.model.find(conditions).then(function (docs) {
226252
return _bluebird2.default.all(docs.map(function (doc, i) {
@@ -306,4 +332,18 @@ var defaultOptions = {
306332
// works correctly
307333
};var toJSON = function toJSON(obj) {
308334
return JSON.parse(JSON.stringify(obj));
335+
};
336+
337+
// helper function to merge query conditions after an update has happened
338+
// usefull if a property which was initially defined in _conditions got overwritten
339+
// with the update
340+
var mergeQueryConditionsWithUpdate = function mergeQueryConditionsWithUpdate(_conditions, _update) {
341+
var update = _update ? _update.$set || _update : _update;
342+
var conditions = Object.assign({}, conditions, update);
343+
344+
// excluding updates other than $set
345+
Object.keys(conditions).forEach(function (key) {
346+
if (key.includes('$')) delete conditions[key];
347+
});
348+
return conditions;
309349
};

package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
"description": "Mongoose plugin that saves a history of JSON patch operations for all documents belonging to a schema in an associated 'patches' collection",
55
"main": "lib/index.js",
66
"author": "Christoph Werner <[email protected]>",
7+
"contributors": [
8+
{
9+
"name": "Robin Weinreich",
10+
"email": "[email protected]"
11+
},
12+
{
13+
"name": "Brett Ausmeier",
14+
"email": "[email protected]"
15+
}
16+
],
717
"license": "MIT",
818
"dependencies": {
919
"fast-json-patch": "^2.0.4",
@@ -73,4 +83,4 @@
7383
"Json Patch",
7484
"JSON PATCH"
7585
]
76-
}
86+
}

src/index.js

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ const defaultOptions = {
4545
// works correctly
4646
const toJSON = obj => JSON.parse(JSON.stringify(obj))
4747

48+
// helper function to merge query conditions after an update has happened
49+
// usefull if a property which was initially defined in _conditions got overwritten
50+
// with the update
51+
const mergeQueryConditionsWithUpdate = (_conditions, _update) => {
52+
const update = _update ? _update.$set || _update : _update
53+
const conditions = Object.assign({}, conditions, update)
54+
55+
// excluding updates other than $set
56+
Object.keys(conditions).forEach(key => {
57+
if (key.includes('$')) delete conditions[key]
58+
})
59+
return conditions
60+
}
61+
4862
export default function(schema, opts) {
4963
const options = merge({}, defaultOptions, opts)
5064

@@ -206,6 +220,7 @@ export default function(schema, opts) {
206220
this.model
207221
.findOne(this._conditions)
208222
.then(original => {
223+
if (original) this._originalId = original._id
209224
original = original || new this.model({})
210225
this._original = toJSON(original.data())
211226
})
@@ -227,11 +242,13 @@ export default function(schema, opts) {
227242
function postUpdateOne(result, next) {
228243
if (result.nModified === 0) return
229244

230-
const conditions = Object.assign(
231-
{},
232-
this._conditions,
233-
this._update.$set || this._update
234-
)
245+
let conditions
246+
if (this._originalId) conditions = { _id: { $eq: this._originalId } }
247+
else
248+
conditions = mergeQueryConditionsWithUpdate(
249+
this._conditions,
250+
this._update
251+
)
235252

236253
this.model
237254
.findOne(conditions)
@@ -250,11 +267,15 @@ export default function(schema, opts) {
250267
function preUpdateMany(next) {
251268
this.model
252269
.find(this._conditions)
253-
.then((originals = []) => {
254-
this._originals = []
255-
.concat(originals)
256-
.map(original => original || new this.model({}))
257-
.map(original => toJSON(original.data()))
270+
.then(originals => {
271+
const originalIds = []
272+
const originalData = []
273+
for (const original of originals) {
274+
originalIds.push(original._id)
275+
originalData.push(toJSON(original.data()))
276+
}
277+
this._originalIds = originalIds
278+
this._originals = originalData
258279
})
259280
.then(() => next())
260281
.catch(next)
@@ -263,11 +284,13 @@ export default function(schema, opts) {
263284
function postUpdateMany(result, next) {
264285
if (result.nModified === 0) return
265286

266-
const conditions = Object.assign(
267-
{},
268-
this._conditions,
269-
this._update.$set || this._update
270-
)
287+
let conditions
288+
if (this._originalIds.length === 0)
289+
conditions = mergeQueryConditionsWithUpdate(
290+
this._conditions,
291+
this._update
292+
)
293+
else conditions = { _id: { $in: this._originalIds } }
271294

272295
this.model
273296
.find(conditions)

test/index.js

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mongoose.Promise = Promise
88
const ObjectId = mongoose.Types.ObjectId
99

1010
const CommentSchema = new Schema({ text: String })
11-
CommentSchema.virtual('user').set(function(user) {
11+
CommentSchema.virtual('user').set(function (user) {
1212
this._user = user
1313
})
1414
CommentSchema.plugin(patchHistory, {
@@ -27,7 +27,13 @@ CommentSchema.plugin(patchHistory, {
2727
},
2828
})
2929

30-
const PostSchema = new Schema({ title: String }, { timestamps: true })
30+
const PostSchema = new Schema(
31+
{
32+
title: String,
33+
tags: { type: [String], default: void 0 },
34+
},
35+
{ timestamps: true }
36+
)
3137
PostSchema.plugin(patchHistory, {
3238
mongoose,
3339
name: 'postPatches',
@@ -39,10 +45,10 @@ PostSchema.plugin(patchHistory, {
3945
},
4046
})
4147

42-
PostSchema.virtual('user').set(function(user) {
48+
PostSchema.virtual('user').set(function (user) {
4349
this.__user = user
4450
})
45-
PostSchema.virtual('reason').set(function(reason) {
51+
PostSchema.virtual('reason').set(function (reason) {
4652
this.__reason = reason
4753
})
4854

@@ -58,15 +64,22 @@ const SportSchema = new Schema({
5864
})
5965
SportSchema.plugin(patchHistory, { mongoose, name: 'sportPatches' })
6066

67+
const PricePoolSchema = new Schema({
68+
name: { type: String },
69+
prices: [{ name: { type: String }, value: { type: Number } }],
70+
})
71+
PricePoolSchema.plugin(patchHistory, { mongoose, name: 'pricePoolPatches' })
72+
6173
describe('mongoose-patch-history', () => {
62-
let Comment, Post, Fruit, Sport, User
74+
let Comment, Post, Fruit, Sport, User, PricePool
6375

6476
before(done => {
6577
Comment = mongoose.model('Comment', CommentSchema)
6678
Post = mongoose.model('Post', PostSchema)
6779
Fruit = mongoose.model('Fruit', FruitSchema)
6880
Sport = mongoose.model('Sport', SportSchema)
6981
User = mongoose.model('User', new Schema())
82+
PricePool = mongoose.model('PricePool', PricePoolSchema)
7083

7184
mongoose
7285
.connect('mongodb://localhost/mongoose-patch-history', {
@@ -85,7 +98,9 @@ describe('mongoose-patch-history', () => {
8598
Sport.Patches.deleteMany({}),
8699
Post.deleteMany({}),
87100
Post.Patches.deleteMany({}),
88-
User.deleteMany({})
101+
User.deleteMany({}),
102+
PricePool.deleteMany({}),
103+
PricePool.Patches.deleteMany({})
89104
)
90105
.then(() => User.create())
91106
.then(() => done())
@@ -301,6 +316,29 @@ describe('mongoose-patch-history', () => {
301316
.then(done)
302317
.catch(done)
303318
})
319+
320+
it('handles array filters', done => {
321+
PricePool.create({
322+
name: 'test',
323+
prices: [
324+
{ name: 'test1', value: 1 },
325+
{ name: 'test2', value: 2 },
326+
],
327+
})
328+
.then(pricePool =>
329+
PricePool.updateOne(
330+
{ name: pricePool.name },
331+
{ $set: { 'prices.$[elem].value': 3 } },
332+
{ arrayFilters: [{ 'elem.name': { $eq: 'test1' } }] }
333+
)
334+
)
335+
.then(res => PricePool.Patches.find({}))
336+
.then(patches => {
337+
assert.equal(patches.length, 2)
338+
})
339+
.then(done)
340+
.catch(done)
341+
})
304342
})
305343

306344
describe('updating a document via updateMany()', () => {
@@ -336,6 +374,55 @@ describe('mongoose-patch-history', () => {
336374
.then(done)
337375
.catch(done)
338376
})
377+
378+
it('handles the $push operator', done => {
379+
Post.create({ title: 'tagged1', tags: ['match'] })
380+
.then(post =>
381+
Post.updateMany(
382+
{ _id: post._id },
383+
{ $push: { tags: 'match2' } },
384+
{ timestamps: false }
385+
)
386+
)
387+
.then(() => Post.find({ title: 'tagged1' }))
388+
.then(posts =>
389+
posts[0].patches.find({ ref: posts[0]._id }).sort({ _id: 1 })
390+
)
391+
.then(patches => {
392+
assert.equal(patches.length, 2)
393+
assert.equal(
394+
JSON.stringify(patches[1].ops),
395+
JSON.stringify([{ op: 'add', path: '/tags/1', value: 'match2' }])
396+
)
397+
})
398+
.then(() => done())
399+
.catch(done)
400+
})
401+
402+
it('handles the $pull operator', done => {
403+
Post.create({ title: 'tagged2', tags: ['match'] })
404+
.then(post =>
405+
// Remove the 'match' tag from all posts tagged with 'match'
406+
Post.updateMany(
407+
{ tags: 'match' },
408+
{ $pull: { tags: 'match' } },
409+
{ timestamps: false }
410+
)
411+
)
412+
.then(() => Post.find({ title: 'tagged2' }))
413+
.then(posts =>
414+
posts[0].patches.find({ ref: posts[0]._id }).sort({ _id: 1 })
415+
)
416+
.then(patches => {
417+
assert.equal(patches.length, 2)
418+
assert.equal(
419+
JSON.stringify(patches[1].ops),
420+
JSON.stringify([{ op: 'remove', path: '/tags/0' }])
421+
)
422+
})
423+
.then(() => done())
424+
.catch(done)
425+
})
339426
})
340427

341428
describe('upserting a document', () => {
@@ -374,6 +461,21 @@ describe('mongoose-patch-history', () => {
374461
.then(done)
375462
.catch(done)
376463
})
464+
465+
it('with updateMany: adds a patch', done => {
466+
Post.updateMany(
467+
{ title: 'upsert2' },
468+
{ title: 'upsert3' },
469+
{ upsert: true }
470+
)
471+
.then(() => Post.find({ title: 'upsert3' }))
472+
.then(posts => posts[0].patches.find({ ref: posts[0].id }))
473+
.then(patches => {
474+
assert.equal(patches.length, 1)
475+
})
476+
.then(done)
477+
.catch(done)
478+
})
377479
})
378480

379481
describe('update with multi', () => {

0 commit comments

Comments
 (0)