diff --git a/README.md b/README.md index c7f2774..0336e5e 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,23 @@ PetSchema.plugin(mongoose_delete, { indexFields: ['deletedAt'] }); ``` +### Custom field names or schema type definition + +```javascript +var mongoose_delete = require('mongoose-delete'); + +var PetSchema = new Schema({ + name: String +}); + +// Add a custom name for each property, will create alias for the original name +PetSchema.plugin(mongoose_delete, { deletedBy: 'deleted_by', deletedAt: 'deleted_at' }); + +// Use custom schema type definition by supplying an object +PetSchema.plugin(mongoose_delete, { deletedBy: { name: 'deleted_by', default: 'None', type: String }, deletedAt: { alias: 'deletedTimestamp' } }); +``` +Expects a Mongoose [Schema Types](https://mongoosejs.com/docs/schematypes.html#schematype-options) object with the added option of `name`. + ## License The MIT License diff --git a/index.js b/index.js index 8f31297..7f275a6 100644 --- a/index.js +++ b/index.js @@ -75,6 +75,34 @@ function createSchemaObject (typeKey, typeValue, options) { return options; } +function deletedFieldName(deletedValue, fallbackName) { + if (typeof deletedValue === 'string') { + return deletedValue; + } else if (typeof deletedValue === 'object' && deletedValue.name) { + return deletedValue.name; + } else if (deletedValue === true || typeof deletedValue === 'object') { + return fallbackName; + } +} + +function addDeletedFieldIfExist(schema, deletedOption, deletedFieldOriginalName, typeKey, type, index) { + const name = deletedFieldName(deletedOption, deletedFieldOriginalName); + if (name) { + const schemaOptions = { + [typeKey]: type, + index + }; + if (name !== deletedFieldOriginalName) { + schemaOptions.alias = deletedFieldOriginalName; + } + if (typeof deletedOption === 'object') { + const { name, ...options } = deletedOption; + Object.assign(schemaOptions, options); + } + schema.add({ [name]: schemaOptions }); + } +} + module.exports = function (schema, options) { options = options || {}; var indexFields = parseIndexFields(options); @@ -84,6 +112,9 @@ module.exports = function (schema, options) { var mainUpdateMethod = mongooseMajorVersion < 5 ? 'update' : 'updateMany'; var mainUpdateWithDeletedMethod = mainUpdateMethod + 'WithDeleted'; + const deletedAtField = deletedFieldName(options.deletedAt, 'deletedAt'); + const deletedByField = deletedFieldName(options.deletedBy, 'deletedBy'); + function updateDocumentsByQuery(schema, conditions, updateQuery, callback) { if (schema[mainUpdateWithDeletedMethod]) { return schema[mainUpdateWithDeletedMethod](conditions, updateQuery, { multi: true }, callback); @@ -93,14 +124,8 @@ module.exports = function (schema, options) { } schema.add({ deleted: createSchemaObject(typeKey, Boolean, { default: false, index: indexFields.deleted }) }); - - if (options.deletedAt === true) { - schema.add({ deletedAt: createSchemaObject(typeKey, Date, { index: indexFields.deletedAt }) }); - } - - if (options.deletedBy === true) { - schema.add({ deletedBy: createSchemaObject(typeKey, options.deletedByType || Schema.Types.ObjectId, { index: indexFields.deletedBy }) }); - } + addDeletedFieldIfExist(schema, options.deletedAt, 'deletedAt', typeKey, Date, indexFields.deletedAt); + addDeletedFieldIfExist(schema, options.deletedBy, 'deletedBy', typeKey, options.deletedByType || Schema.Types.ObjectId, indexFields.deletedBy); var use$neOperator = true; if (options.use$neOperator !== undefined && typeof options.use$neOperator === "boolean") { @@ -238,12 +263,12 @@ module.exports = function (schema, options) { this.deleted = true; - if (schema.path('deletedAt')) { - this.deletedAt = new Date(); + if (deletedAtField && schema.path(deletedAtField)) { + this[deletedAtField] = new Date(); } - if (schema.path('deletedBy')) { - this.deletedBy = deletedBy; + if (deletedByField && schema.path(deletedByField)) { + this[deletedByField] = deletedBy; } if (options.validateBeforeDelete === false) { @@ -268,12 +293,12 @@ module.exports = function (schema, options) { deleted: true }; - if (schema.path('deletedAt')) { - doc.deletedAt = new Date(); + if (deletedAtField && schema.path(deletedAtField)) { + doc[deletedAtField] = new Date(); } - if (schema.path('deletedBy')) { - doc.deletedBy = deletedBy; + if (deletedByField && schema.path(deletedByField)) { + doc[deletedByField] = deletedBy; } return updateDocumentsByQuery(this, conditions, doc, callback); @@ -294,8 +319,12 @@ module.exports = function (schema, options) { schema.methods.restore = function (callback) { this.deleted = false; - this.deletedAt = undefined; - this.deletedBy = undefined; + if (deletedAtField) { + this[deletedAtField] = undefined; + } + if (deletedByField) { + this[deletedByField] = undefined; + } return this.save(callback); }; @@ -306,11 +335,16 @@ module.exports = function (schema, options) { } var doc = { - deleted: false, - deletedAt: undefined, - deletedBy: undefined + deleted: false }; + if (deletedAtField) { + doc[deletedAtField] = undefined; + } + if (deletedByField) { + doc[deletedByField] = undefined; + } + return updateDocumentsByQuery(this, conditions, doc, callback); }; }; diff --git a/test/index.js b/test/index.js index b924423..aec0a16 100644 --- a/test/index.js +++ b/test/index.js @@ -380,6 +380,55 @@ describe("mongoose_delete with options: { deletedAt : true }, using option: type }); }); +describe("mongoose_delete with options: { deletedAt : 'deleted_at' }, using custom field name", function () { + const Test2Schema = new Schema({name: String}, {collection: 'mongoose_delete_test2b'}); + Test2Schema.plugin(mongoose_delete, {deletedAt: 'deleted_at'}); + const Test2 = mongoose.model('Test2b', Test2Schema); + const puffy1 = new Test2({name: 'Puffy1'}); + const puffy2 = new Test2({name: 'Puffy2'}); + + before(async function () { + await puffy1.save(); + await puffy2.save() + }); + + after(async function () { + await mongoose.connection.db.dropCollection("mongoose_delete_test3b"); + }); + + it("delete() -> should save 'deletedAt' key", async function () { + const puffy = await Test2.findOne({name: 'Puffy1'}); + const success = await puffy.delete(); + + expect(success).to.have.property('deleted').that.equal(true); + expect(success).to.have.property('deleted_at').that.is.an('date') + expect(success.deletedAt).that.is.an('date') + }); + + it("deleteById() -> should save `deletedAt` key", async function () { + const documents = await Test2.deleteById(puffy2._id); + + expect(documents).to.be.mongoose_ok(); + expect(documents).to.be.mongoose_count(1); + + const doc = await Test2.findOne({name: 'Puffy2'}); + + expect(doc).to.have.property('deleted').that.equal(true); + expect(doc).to.have.property('deleted_at').that.is.an('date') + expect(doc.deletedAt).that.is.an('date') + }); + + it("restore() -> should set deleted:false and delete `deletedAt` key", async function () { + const puffy = await Test2.findOne({name: 'Puffy1'}) + + const success = await puffy.restore(); + + expect(success).to.have.property('deleted').that.equal(false); + expect(success).to.have.property('deleted_at').that.not.exist; + expect(success.deletedAt).that.not.exist; + }); +}); + describe("mongoose_delete with options: { deletedBy : true }", function () { var Test3Schema = new Schema({name: String}, {collection: 'mongoose_delete_test3'}); @@ -587,6 +636,108 @@ describe("mongoose_delete with options: { deletedBy : true, deletedByType: Strin }); }); +describe("mongoose_delete with options: { deletedBy : 'deleted_by' }, using custom field name", function () { + + var Test3Schema = new Schema({name: String}, {collection: 'mongoose_delete_test3b'}); + Test3Schema.plugin(mongoose_delete, {deletedBy: 'deleted_by'}); + var Test3 = mongoose.model('Test3b', Test3Schema); + var puffy1 = new Test3({name: 'Puffy1'}); + var puffy2 = new Test3({name: 'Puffy2'}); + + before(async function () { + await puffy1.save(); + await puffy2.save() + }); + + after(async function () { + await mongoose.connection.db.dropCollection("mongoose_delete_test3b"); + }); + + var id = mongoose.Types.ObjectId("53da93b16b4a6670076b16bf"); + + it("delete() -> should save 'deletedBy' key", async function () { + const puffy = await Test3.findOne({name: 'Puffy1'}); + const success = await puffy.delete(id); + + expect(success).to.have.property('deleted').that.equal(true); + expect(success).to.have.property('deleted_by').that.deep.equal(id); + expect(success.deletedBy).that.deep.equal(id); + }); + + it("deleteById() -> should save `deletedBy` key", async function () { + const documents = await Test3.deleteById(puffy2._id, id); + + expect(documents).to.be.mongoose_ok(); + expect(documents).to.be.mongoose_count(1); + + const doc = await Test3.findOne({name: 'Puffy2'}); + + expect(doc).to.have.property('deleted').that.equal(true); + expect(doc).to.have.property('deleted_by').that.deep.equal(id); + expect(doc.deletedBy).that.deep.equal(id); + }); + + it("restore() -> should set deleted:false and delete `deletedBy` key", async function () { + const puffy = await Test3.findOne({name: 'Puffy1'}) + + const success = await puffy.restore(); + + expect(success).to.have.property('deleted').that.equal(false); + expect(success).to.have.property('deleted_by').that.not.exist; + expect(success.deletedBy).that.not.exist; + }); +}); + +describe("mongoose_delete with options: { deletedBy : { ... }, using custom schema", function () { + + var Test3Schema = new Schema({name: String}, {collection: 'mongoose_delete_test3c'}); + Test3Schema.plugin(mongoose_delete, {deletedBy: { name: 'deleted_by', get: (id) => { return id && { id }; }, type: String }}); + var Test3 = mongoose.model('Test3c', Test3Schema); + var puffy1 = new Test3({name: 'Puffy1'}); + var puffy2 = new Test3({name: 'Puffy2'}); + + before(async function () { + await puffy1.save(); + await puffy2.save() + }); + + after(async function () { + await mongoose.connection.db.dropCollection("mongoose_delete_test3c"); + }); + + var id = '53da93b16b4a6670076b16bf'; + + it("delete() -> should save 'deletedBy' key", async function () { + const puffy = await Test3.findOne({name: 'Puffy1'}); + const success = await puffy.delete(id); + + expect(success).to.have.property('deleted').that.equal(true); + expect(success.deletedBy).that.deep.equal({ id }); + }); + + it("deleteById() -> should save `deletedBy` key", async function () { + const documents = await Test3.deleteById(puffy2._id, id); + + expect(documents).to.be.mongoose_ok(); + expect(documents).to.be.mongoose_count(1); + + const doc = await Test3.findOne({name: 'Puffy2'}); + + expect(doc).to.have.property('deleted').that.equal(true); + expect(doc.deletedBy).that.deep.equal({ id }); + }); + + it("restore() -> should set deleted:false and delete `deletedBy` key", async function () { + const puffy = await Test3.findOne({name: 'Puffy1'}) + + const success = await puffy.restore(); + + expect(success).to.have.property('deleted').that.equal(false); + expect(success).to.have.property('deletedBy').that.not.exist; + expect(success.deletedBy).that.not.exist; + }); +}); + describe("check not overridden static methods", function () { var TestSchema = new Schema({name: String}, {collection: 'mongoose_delete_test'}); TestSchema.plugin(mongoose_delete);