Skip to content

Commit

Permalink
Merge pull request #5 from ilovepixelart/feature/coverage
Browse files Browse the repository at this point in the history
Should update many
  • Loading branch information
ilovepixelart authored Apr 9, 2023
2 parents 95b0248 + add0ee8 commit 6b9e702
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 66 deletions.
95 changes: 29 additions & 66 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,6 @@ function getObjects<T> (opts: IPluginOptions<T>, current: HydratedDocument<T>, o
return { currentObject, originalObject }
}

async function bulkPatch<T> (opts: IPluginOptions<T>, context: IContext<T>): Promise<void> {
const chunks = _.chunk(context.deletedDocs, 1000)
for await (const chunk of chunks) {
const bulk = []
for (const oldDoc of chunk) {
if (opts.eventDeleted) {
em.emit(opts.eventDeleted, { oldDoc })
}
if (!opts.patchHistoryDisabled) {
bulk.push({
insertOne: {
document: {
op: context.op,
modelName: context.modelName,
collectionName: context.collectionName,
collectionId: oldDoc._id as Types.ObjectId,
doc: oldDoc,
version: 0
}
}
})
}
}

if (opts.patchHistoryDisabled) continue
await History.bulkWrite(bulk, { ordered: false }).catch((err: MongooseError) => {
console.error(err)
})
}
}

async function updatePatch<T> (opts: IPluginOptions<T>, context: IContext<T>, current: HydratedDocument<T>, original: HydratedDocument<T>): Promise<void> {
const { currentObject, originalObject } = getObjects(opts, current, original)

Expand Down Expand Up @@ -204,39 +173,6 @@ export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: I
}
})

schema.pre('updateMany', options, async function (this: IHookContext<T>, next) {
const filter = this.getFilter()
const options = this.getOptions()
const ignore = options.__ignore as boolean

const context: IContext<T> = {
op: this.op,
modelName: opts.modelName ?? this.model.modelName,
collectionName: opts.collectionName ?? this.model.collection.collectionName,
isNew: options.upsert
}

if (!ignore) {
const ids = await this.model.distinct<Types.ObjectId>('_id', filter).exec()
context.updatedIds = ids
}

this._context = context

next()
})

schema.post('updateMany', options, async function (this: IHookContext<T>) {
if (this._context.updatedIds?.length) return

const cursor = this.model.find({ _id: { $in: this._context.updatedIds } }).cursor()
await cursor.eachAsync((current: HydratedDocument<T>) => {
if (opts.eventUpdated) {
em.emit(opts.eventUpdated, { doc: current })
}
})
})

schema.pre(['remove', 'findOneAndDelete', 'findOneAndRemove', 'deleteOne', 'deleteMany'], options, async function (this: IHookContext<T>, next) {
const filter = this.getFilter()
const options = this.getOptions()
Expand All @@ -260,8 +196,35 @@ export const patchHistoryPlugin = function plugin<T> (schema: Schema<T>, opts: I
})

schema.post(['remove', 'findOneAndDelete', 'findOneAndRemove', 'deleteOne', 'deleteMany'], options, async function (this: IHookContext<T>) {
if (_.isEmpty(this._context.deletedDocs)) return
if (_.isEmpty(this._context.deletedDocs) || (!opts.eventDeleted && opts.patchHistoryDisabled)) return

const chunks = _.chunk(this._context.deletedDocs, 1000)
for await (const chunk of chunks) {
const bulk = []
for (const oldDoc of chunk) {
if (opts.eventDeleted) {
em.emit(opts.eventDeleted, { oldDoc })
}
if (!opts.patchHistoryDisabled) {
bulk.push({
insertOne: {
document: {
op: this._context.op,
modelName: this._context.modelName,
collectionName: this._context.collectionName,
collectionId: oldDoc._id as Types.ObjectId,
doc: oldDoc,
version: 0
}
}
})
}
}

await bulkPatch(opts, this._context)
if (opts.patchHistoryDisabled) continue
await History.bulkWrite(bulk, { ordered: false }).catch((err: MongooseError) => {
console.error(err)
})
}
})
}
50 changes: 50 additions & 0 deletions tests/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,54 @@ describe('plugin', () => {
expect(first.doc.name).toBe('Bob')
expect(first.version).toBe(0)
})

it('should update many', async () => {
const john = await User.create({ name: 'John', role: 'user' })
expect(john.name).toBe('John')
const alice = await User.create({ name: 'Alice', role: 'user' })
expect(alice.name).toBe('Alice')

await User.updateMany({ role: 'user' }, { $set: { name: 'Bob' } }).exec()

const history = await History.find({})
expect(history).toHaveLength(4)

const [first, second, third, fourth] = history

expect(first.op).toBe('create')
expect(first.patch).toHaveLength(0)
expect(first.doc.name).toBe('John')
expect(first.doc.role).toBe('user')
expect(first.version).toBe(0)

expect(second.op).toBe('create')
expect(second.patch).toHaveLength(0)
expect(second.doc.name).toBe('Alice')
expect(second.doc.role).toBe('user')
expect(second.version).toBe(0)

expect(third.op).toBe('updateMany')
expect(third.patch).toHaveLength(2)
expect(third.patch[1].value).toBe('Bob')
expect(third.version).toBe(1)

expect(fourth.op).toBe('updateMany')
expect(fourth.patch).toHaveLength(2)
expect(fourth.patch[1].value).toBe('Bob')
expect(fourth.version).toBe(1)

expect(em.emit).toHaveBeenCalledTimes(4)
expect(em.emit).toHaveBeenCalledWith(USER_CREATED_EVENT, { doc: first.doc })
expect(em.emit).toHaveBeenCalledWith(USER_CREATED_EVENT, { doc: second.doc })
expect(em.emit).toHaveBeenCalledWith(USER_UPDATED_EVENT, {
oldDoc: expect.objectContaining({ _id: john._id, name: 'John', role: 'user' }),
doc: expect.objectContaining({ _id: john._id, name: 'Bob', role: 'user' }),
patch: third.patch
})
expect(em.emit).toHaveBeenCalledWith(USER_UPDATED_EVENT, {
oldDoc: expect.objectContaining({ _id: alice._id, name: 'Alice', role: 'user' }),
doc: expect.objectContaining({ _id: alice._id, name: 'Bob', role: 'user' }),
patch: fourth.patch
})
})
})

0 comments on commit 6b9e702

Please sign in to comment.