From 40d8f6bd82c9b6b6fb96865988aa1a5af43128cc Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Wed, 13 Jun 2012 00:22:36 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + LICENSE | 19 +++ README.md | 58 +++++++ index.js | 49 ++++++ package.json | 20 +++ test/mongoose-lifecycle.test.js | 288 ++++++++++++++++++++++++++++++++ 6 files changed, 435 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 index.js create mode 100644 package.json create mode 100644 test/mongoose-lifecycle.test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b914c99 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Francois Zaninotto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..78b829d --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +mongoose-lifecycle +================== + +Mongoose plugin adding lifecyle events on the model class. + +Installation +------------ + +Add the plugin as a dependency to your project in `package.json`: + +```javascript +{ + "name": "myproject", + ... + "dependencies": { + "mongoose": "2.6.5", + "mongoose-lifecycle": "1.0.0", + ... + }, +} +``` + +And run `npm install` again. + +Usage +----- + +Initialization is straightforward: + +```javascript +var Book = new Schema({ ... }); +Book.plugin(require('mongoose-lifecycle')); +``` + +Now the model emits lifecycle events before and after persistence operations: + + - beforeInsert + - afterInsert + - beforeUpdate + - afterUpdate + - beforeSave (called for both inserts and updates) + - afterSave (called for both inserts and updates) + - beforeRemove + - afterRemove + +You can listen to these events directly on the model. + +```javascript +var Book = require('path/to/models/book'); +Book.on('beforeInsert', function(book) { + // do stuff... +}); +``` + +License +------- + +MIT License \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..fe92207 --- /dev/null +++ b/index.js @@ -0,0 +1,49 @@ +/** + * Mongoose plugin adding lifecyle events on the model class. + * + * Initialization is straightforward: + * + * var lifecycleEventsPlugin = require('path/to/mongoose-lifecycle'); + * var Book = new Schema({ ... }); + * Book.plugin(lifecycleEventsPlugin); + * + * Now the model emits lifecycle events before and after persistence operations: + * + * - beforeInsert + * - afterInsert + * - beforeUpdate + * - afterUpdate + * - beforeSave (called for both inserts and updates) + * - afterSave (called for both inserts and updates) + * - beforeRemove + * - afterRemove + * + * You can listen to these events directly on the model. + * + * var Book = require('path/to/models/book'); + * Book.on('beforeInsert', function(book) { + * // do stuff... + * }); + */ +module.exports = exports = function lifecycleEventsPlugin(schema) { + schema.pre('save', function (next) { + var model = this.model(this.constructor.modelName); + model.emit('beforeSave', this); + this.isNew ? model.emit('beforeInsert', this) : model.emit('beforeUpdate', this); + this._isNew_internal = this.isNew; + next(); + }); + schema.post('save', function() { + var model = this.model(this.constructor.modelName); + model.emit('afterSave', this); + this._isNew_internal ? model.emit('afterInsert', this) : model.emit('afterUpdate', this); + this._isNew_internal = undefined; + }); + schema.pre('remove', function (next) { + this.model(this.constructor.modelName).emit('beforeRemove', this); + next(); + }); + schema.post('remove', function() { + this.model(this.constructor.modelName).emit('afterRemove', this); + }); +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c2125ee --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "mongoose-lifecycle", + "description": "Mongoose plugin adding lifecyle events on the model class", + "version": "1.0.0", + "author": "Francois Zaninotto", + "keywords": ["mongodb", "mongoose", "plugin", "presave", "beforesave"], + "repository": { + "type": "git", + "url": "git://github.com/fzaninotto/mongoose-lifecycle" + }, + "devDependencies": { + "should": ">=0.2.1", + "mongoose": ">=2.6.5", + "cli-table" : ">=0.0.1" + }, + "license": "MIT", + "engine": { + "node": ">=0.6" + } +} diff --git a/test/mongoose-lifecycle.test.js b/test/mongoose-lifecycle.test.js new file mode 100644 index 0000000..2ad1a01 --- /dev/null +++ b/test/mongoose-lifecycle.test.js @@ -0,0 +1,288 @@ +// mocha tests - run them with 'mocha -R spec' +var should = require('should'); +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; +var ObjectId = Schema.ObjectId; +var lifecycleEventsPlugin = require('../index.js'); + +mongoose.connect('mongodb://localhost/mongoose-lifecycle-test'); +mongoose.connection.on('error', function (err) { + console.error('MongoDB error: ' + err.message); + console.error('Make sure a mongoDB server is running and accessible by this application') +}); + +// Setup Schemas for testing. +var PostSchema = new Schema({ + title: String, + slug: String +}); +PostSchema.plugin(lifecycleEventsPlugin); +var Post = mongoose.model('Post', PostSchema); +Post.remove({}, function () { }); + +describe('Events', function(){ + + describe('#beforeSave', function(){ + + it('should trigger before insert', function(done){ + Post.on('beforeSave', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title.should.eql('foo'); + should.not.exist(post._delta()); + Post.removeAllListeners('beforeSave'); + done(); + }); + }); + + it('should trigger before update', function(done){ + Post.on('beforeSave', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title = ''; + post.slug = 'foo'; + post.save(function(err2) { + if (err2) throw err2; + post.title.should.eql('foo'); + should.not.exist(post._delta()); + Post.removeAllListeners('beforeSave'); + done(); + }); + }); + }); + + }); + + describe('#beforeInsert', function(){ + + it('should trigger before insert', function(done){ + Post.on('beforeInsert', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title.should.eql('foo'); + should.not.exist(post._delta()); + Post.removeAllListeners('beforeInsert'); + done(); + }); + }); + + it('should not trigger before update', function(done){ + Post.on('beforeInsert', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title = ''; + post.slug = 'foo'; + post.save(function(err2) { + if (err2) throw err2; + post.title.should.eql(''); + should.not.exist(post._delta()); + Post.removeAllListeners('beforeInsert'); + done(); + }); + }); + }); + + }); + + describe('#beforeUpdate', function(){ + + it('should not trigger before insert', function(done){ + Post.on('beforeUpdate', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.should.not.have.property('title'); + should.not.exist(post._delta()); + Post.removeAllListeners('beforeUpdate'); + done(); + }); + }); + + it('should trigger before update', function(done){ + Post.on('beforeUpdate', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title = ''; + post.slug = 'foo'; + post.save(function(err2) { + if (err2) throw err2; + post.title.should.eql('foo'); + should.not.exist(post._delta()); + Post.removeAllListeners('beforeUpdate'); + done(); + }); + }); + }); + + }); + + describe('#afterSave', function(){ + + it('should trigger after insert', function(done){ + Post.on('afterSave', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title.should.eql('foo'); + should.exist(post._delta()); + Post.removeAllListeners('afterSave'); + done(); + }); + }); + + it('should trigger after update', function(done){ + Post.on('afterSave', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title = ''; + post.slug = 'foo'; + post.save(function(err2) { + if (err2) throw err2; + post.title.should.eql('foo'); + should.exist(post._delta()); + Post.removeAllListeners('afterSave'); + done(); + }); + }); + }); + + }); + + describe('#afterInsert', function(){ + + it('should trigger after insert', function(done){ + Post.on('afterInsert', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title.should.eql('foo'); + should.exist(post._delta()); + Post.removeAllListeners('afterInsert'); + done(); + }); + }); + + it('should not trigger after update', function(done){ + Post.on('afterInsert', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title = ''; + post.slug = 'foo'; + post.save(function(err2) { + if (err2) throw err2; + post.title.should.eql(''); + should.not.exist(post._delta()); + Post.removeAllListeners('afterInsert'); + done(); + }); + }); + }); + + }); + + describe('#afterUpdate', function(){ + + it('should not trigger after insert', function(done){ + Post.on('afterUpdate', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.should.not.have.property('title'); + should.not.exist(post._delta()); + Post.removeAllListeners('afterUpdate'); + done(); + }); + }); + + it('should trigger after update', function(done){ + Post.on('afterUpdate', function(post) { + post.title = 'foo'; + }); + var post = new Post(); + post.should.not.have.property('title'); + post.save(function(err){ + if (err) throw err; + post.title = ''; + post.slug = 'foo'; + post.save(function(err2) { + if (err2) throw err2; + post.title.should.eql('foo'); + should.exist(post._delta()); + Post.removeAllListeners('afterUpdate'); + done(); + }); + }); + }); + + }); + + describe('#beforeRemove', function(){ + + it('should trigger on remove', function(done){ + var flag = false; + Post.on('beforeRemove', function(post) { + flag = true; + }); + var post = new Post(); + flag.should.be.false; + post.save(function(err){ + if (err) throw err; + flag.should.be.false; + post.remove(function(err2) { + if (err2) throw err2; + flag.should.be.true; + Post.removeAllListeners('beforeRemove'); + done(); + }); + }); + }); + + }); + + describe('#afterRemove', function(){ + + it('should trigger after remove'); + + }); + +}); \ No newline at end of file