├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── package.json └── test ├── dbmodels ├── Author.js └── Book.js ├── errormodels └── WrongSchema.js ├── errormodels2 └── WrongPlugin.js ├── mocha.opts ├── pluginsmodels └── Plugged.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | *.swp 4 | *.swo 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | services: 3 | - mongodb 4 | node_js: 5 | - "0.10" 6 | - "0.12" 7 | - "4.2" 8 | notifications: 9 | email: false 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mongoose-simpledb 2 | 3 | [![Build Status](https://travis-ci.org/codetunnel/mongoose-simpledb.png)](https://travis-ci.org/codetunnel/mongoose-simpledb) 4 | [![Dependencies Status](https://gemnasium.com/codetunnel/mongoose-simpledb.png)](https://gemnasium.com/codetunnel/mongoose-simpledb) 5 | [![NPM version](https://badge.fury.io/js/mongoose-simpledb.png)](http://badge.fury.io/js/mongoose-simpledb) 6 | 7 | > Simple API for defining mongoose models and loading them into a single object for easy access. 8 | 9 | ## Getting Started 10 | 11 | > npm install mongoose-simpledb 12 | 13 | Note: You do not need to install mongoose. Simpledb is intended to hide mongoose so you never have to install or require it. 14 | 15 | After installing simpledb you'll want to define your mongoose models. By default simpledb looks in the root of your project for a directory called "dbmodels" and will load all model files found there. However, you can place your models wherever you wish and pass the location in simpledb's options. Let's look at an example model file. 16 | 17 | ```javascript 18 | // dbmodels/Comment.js 19 | 20 | var ObjectId = require('mongoose-simpledb').Types.ObjectId; 21 | 22 | exports.schema = { 23 | creator: { type: ObjectId, ref: 'User' }, 24 | blogPost: { type: Number, ref: 'BlogPost' }, 25 | url: String, 26 | body: String, 27 | date: { type: Date, default: Date.now }, 28 | editedDate: Date, 29 | editedBy: { type: ObjectId, ref: 'User' } 30 | }; 31 | ``` 32 | 33 | Once you have a model file you can get reference to simpledb and call its `init` function. You can pass a callback function to `init` that will receive the `db` object when all of your models have finished being loaded into it. Or you can assign the results of the `init` function to a variable which will be lazy-loaded with your models when they are done being loaded; 34 | 35 | Callback: 36 | 37 | ```javascript 38 | var simpledb = require('mongoose-simpledb'); 39 | simpledb.init(function (err, db) { 40 | if (err) return console.error(err); 41 | // You can safely assume that db is populated with your models. 42 | db.Comment.find({ blogPost: 123 }, ...): 43 | }); 44 | ``` 45 | 46 | Lazy-loaded reference: 47 | 48 | ```javascript 49 | var simpledb = require('mongoose-simpledb'); 50 | var db = simpledb.init(); 51 | // After a time... 52 | db.Comment.find({ blogPost: 123 }, ...); 53 | ``` 54 | 55 | If you prefer to use the lazy-loaded option then you can check `db.modelsLoaded` to see if the object is ready to be used. The only requirement of a model file is that you expose a property called `schema`. simpledb will use this property when creating your Mongoose schema. While `schema` is the only required property for you to define, you can define a few others as well if you'd like to setup instance methods, static methods, or virtual properties. 56 | 57 | ## Need instance methods? 58 | 59 | ```javascript 60 | exports.methods = { 61 | dateFromNow: function () { 62 | return moment(this.date).fromNow(); 63 | }, 64 | editedDateFromNow: function () { 65 | return moment(this.editedDate).fromNow(); 66 | } 67 | }; 68 | ``` 69 | 70 | ## What about statics? 71 | 72 | ```javascript 73 | exports.statics = { 74 | tenMostRecent: function (blogPostId, callback) { 75 | return this.where('date').sort('-date').limit(10).exec(callback); 76 | } 77 | }; 78 | ``` 79 | 80 | ## Yes, you can even define virtual properties. 81 | 82 | ```javascript 83 | exports.virtuals = { 84 | bodyHtml: { 85 | get: function () { 86 | return marked(this.body); 87 | } 88 | }, 89 | website: { 90 | get: function () { 91 | return this.url; 92 | }, 93 | set: function (url) { 94 | if (!/^http:\/\//i.test(url)) 95 | url = "http://" + url; 96 | this.url = url; 97 | } 98 | } 99 | }; 100 | ``` 101 | 102 | You can see that when specifying virtuals you can include both "get" and/or "set" as needed for that virtual property. You can also use dot notation with your instance methods in virtuals. Just replace the method/virtual name with a string and use dots. 103 | 104 | ```javascript 105 | // dbmodels/Person.js 106 | 107 | exports.schema = { 108 | name: { 109 | first: String, 110 | last: String 111 | } 112 | }; 113 | 114 | exports.virtuals = { 115 | "name.full": { 116 | get: function () { 117 | return this.name.first + ' ' + this.name.last; 118 | }, 119 | set: function (fullName) { 120 | if (fullName.indexOf(' ') !== -1) { 121 | var segments = fullName.split(' '); 122 | this.name.first = segments[0]; 123 | this.name.last = segments[1]; 124 | } else { 125 | this.name.first = fullName; 126 | } 127 | } 128 | } 129 | }; 130 | ``` 131 | 132 | ## Options 133 | 134 | An options object can be passed to the `init` function. 135 | ```javascript 136 | simpledb.init(options, callback); 137 | ``` 138 | 139 | Available Options and their default values: 140 | 141 | ```javascript 142 | { 143 | // The mongoose connection string to use. 144 | connectionString: 'mongodb:\\localhost', 145 | // The path to the directory where your models are stored. 146 | modelsDir: path.join(__dirname, '..', '..', 'dbmodels'), 147 | // Whether or not simpledb should auto-increment _id's of type Number. 148 | autoIncrementNumberIds: true 149 | } 150 | ``` 151 | 152 | Any of these can be overridden as needed. 153 | 154 | --- 155 | 156 | ## Need a reference to `ObjectId` or other mongoose types? 157 | 158 | One goal of simpledb is to hide mongoose so that you never have to install it or `require` it yourself. One problem with this is that sometimes you need access to mongoose's types. For this reason simpledb exposes `mongoose.Schema.Types` as `simpledb.Types`. 159 | ```javascript 160 | var ObjectId = require('mongoose-simpledb').Types.ObjectId; 161 | ``` 162 | 163 | Then you can use it in your schemas. 164 | 165 | ```javascript 166 | exports.schema = { 167 | creator: { type: ObjectId, ref: 'User' } 168 | }; 169 | ``` 170 | 171 | --- 172 | 173 | ## Want to get rid of `ObjectId` altogether and use a simple incrementing `Number` `_id`? 174 | 175 | Oe feature that Mongoose/MongoDB lack out of the box is the ability to automatically increment a simple integer ID with each new document added to the database. I wrote a mongoose plugin called [mongoose-auto-increment](http://github.com/Chevex/mongoose-auto-increment) that enables this functionality. If you explicitly declare the `_id` field on your schema as type `Number` then simpledb will automatically invoke the mongoose-auto-increment plugin for that model. 176 | 177 | ```javascript 178 | exports.schema = { 179 | _id: Number, // Causes simpledb to auto-increment _id for new documents. 180 | creator: { type: Number, ref: 'User' } 181 | }; 182 | ``` 183 | 184 | --- 185 | 186 | ## Tired of passing the `db` around to other areas of your application? 187 | 188 | In node, modules are cached after they are first grabbed with `require`. Simpledb utilizes this fact to make it extremely easy for you to access your `db` object from anywhere by simply calling `require` again. As long as you've called `init` and enough time has passed for your `db` object's models to be loaded then you can access it. 189 | 190 | ```javascript 191 | var simpledb = require('mongoose-simpledb'); 192 | 193 | simpledb.init(connectionString); 194 | 195 | // After a time... 196 | 197 | var db = require('mongoose-simpledb').db; 198 | ``` 199 | 200 | Remember that you can always check `db.modelsLoaded` to ensure that the object is ready to use. 201 | 202 | --- 203 | 204 | ## Mongoose-simpledb in 30-ish seconds! 205 | 206 | In this screencast graphic you can watch as I wrap a basic [express](http://expressjs.com) app with simpledb. I first wrap the code in app.js inside the `simpledb.init` callback to ensure that `db` is ready to use by the time the server spins up. Next I create a model file at `dbmodels/Person.js` and populate the schema with some basic properties, define a virtual property, and create an example method. Finally, I write a couple quick route handlers that are invoked by a form page so that I can create a `Person` as well as retrieve it. 207 | 208 | [![](http://i.imgur.com/p16GDGP.gif)](http://i.imgur.com/p16GDGP.gif) 209 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'), 2 | fs = require('fs'), 3 | path = require('path'), 4 | autoIncrement = require('mongoose-auto-increment'), 5 | extend = require('extend'); 6 | 7 | module.exports = exports = { 8 | 9 | db: { modelsLoaded: false }, 10 | 11 | init: function () { 12 | var _this = this, 13 | settings = { 14 | // The callback to invoke when models are loaded. 15 | // Default function is empty and does nothing. It's only here to provide a method signature. 16 | callback: function (err, db) {}, 17 | // The mongoose connection string to use. 18 | connectionString: 'mongodb://localhost/test', 19 | // The path to the directory where your dbmodels are stored. 20 | modelsDir: path.join(__dirname, '..', '..', 'dbmodels'), 21 | // Whether or not simpledb should auto-increment _id's of type Number. 22 | autoIncrementNumberIds: true, 23 | autoIncrementSettings: { 24 | field: "_id", // Field name of id, _id by default 25 | startAt: 0, // The number the count should start at. 26 | incrementBy: 1 // The number by which to increment the count each time. 27 | }, 28 | //default options of connect, can be extended, or changed 29 | options: { server: { socketOptions: { keepAlive: 1 } } } 30 | }; 31 | 32 | switch (arguments.length) { 33 | case 1: 34 | switch (typeof arguments[0]) { 35 | // If the only argument is a function, set callback setting. 36 | case 'function': 37 | settings.callback = arguments[0]; 38 | break; 39 | // If the only argument is a string, set the connectionString setting. 40 | case 'string': 41 | settings.connectionString = arguments[0]; 42 | break; 43 | // If the only argument is an object, extend settings with the object. 44 | case 'object': 45 | extend(settings, arguments[0]); 46 | } 47 | break; 48 | case 2: 49 | // If the first arg is a string and the second is a function then set the connectionString and callback settings. 50 | if (typeof arguments[0] === 'string' && typeof arguments[1] === 'function') { 51 | settings.connectionString = arguments[0]; 52 | settings.callback = arguments[1]; 53 | } 54 | // If the first arg is an object and the second is a function then extend settings with the object and set the callback setting. 55 | else if (typeof arguments[0] === 'object' && typeof arguments[1] === 'function') { 56 | extend(settings, arguments[0]); 57 | settings.callback = arguments[1]; 58 | } 59 | break; 60 | } 61 | 62 | // Create db object. 63 | var db = _this.db; 64 | 65 | function resetDb () { 66 | _this.db.modelsLoaded = false; 67 | for (var key in _this.db) { 68 | if (key !== 'modelsLoaded') 69 | delete _this.db[key]; 70 | } 71 | } 72 | resetDb(); 73 | 74 | // Create mongoose connection and attach it to db object. 75 | db.connection = mongoose.createConnection(settings.connectionString, settings.options); 76 | 77 | // If a mongoose error occurs then invoke the callback with the error. 78 | db.connection.on('error', settings.callback); 79 | 80 | // When the connection closes reset the db object. 81 | db.connection.on('close', resetDb); 82 | 83 | // Once the connection is open begin to load models from the database. 84 | db.connection.once('open', function () { 85 | // If mongoose-auto-increment plugin is installedInitialize mongoose-auto-increment plugin. 86 | if (settings.autoIncrementNumberIds) 87 | autoIncrement.initialize(db.connection); 88 | 89 | // Find and load all Mongoose dbmodels from the dbmodels directory. 90 | fs.readdir(settings.modelsDir, function (err, files) { 91 | if (err) return settings.callback(err); 92 | for (var i in files) { 93 | var file = files[i]; 94 | if (path.extname(file) === '.js' || path.extname(file) === '.coffee') { 95 | var modelName = path.basename(file.replace(path.extname(file), '')), 96 | modelData = require(path.join(settings.modelsDir, file)); 97 | 98 | if (!modelData.hasOwnProperty('schema')) 99 | return settings.callback(new Error('Model file ' + file + ' is invalid: No schema.')); 100 | 101 | // Create schema based on template in model file. 102 | var schema; 103 | if (modelData.schemaOptions) 104 | schema = new mongoose.Schema(modelData.schema, modelData.schemaOptions); 105 | else 106 | schema = new mongoose.Schema(modelData.schema); 107 | 108 | // Add any instance methods defined in model file. 109 | extend(schema.methods, modelData.methods); 110 | 111 | // Add any static methods defined in model file. 112 | extend(schema.statics, modelData.statics); 113 | 114 | // Add any virtual properties defined in model file. 115 | for (var key in modelData.virtuals) { 116 | if (modelData.virtuals.hasOwnProperty(key)) { 117 | var virtualData = modelData.virtuals[key]; 118 | if (virtualData.hasOwnProperty('get')) 119 | schema.virtual(key).get(virtualData.get); 120 | if (virtualData.hasOwnProperty('set')) 121 | schema.virtual(key).set(virtualData.set); 122 | } 123 | } 124 | 125 | //Add plugins to schema 126 | for (var key in modelData.plugins) { 127 | var record = modelData.plugins[key]; 128 | if (!record.hasOwnProperty('plugin')) 129 | return settings.callback(new Error('Model file ' + file + ' is invalid: Wrong plugin definition.')); 130 | schema.plugin(record.plugin, record.options); 131 | } 132 | 133 | //Add pre hooks 134 | for (var key in modelData.pre) { 135 | var record = modelData.pre[key]; 136 | schema.pre(key, record); 137 | } 138 | 139 | //Add post hooks 140 | for (var key in modelData.post) { 141 | var record = modelData.post[key]; 142 | schema.post(key, record); 143 | } 144 | 145 | // If autoIncrementIds:true then utilize mongoose-auto-increment plugin for this model. 146 | if (settings.autoIncrementNumberIds) { 147 | var field = settings.autoIncrementSettings.field; 148 | if (schema.paths.hasOwnProperty(field) && schema.paths[field].instance === 'Number') { 149 | settings.autoIncrementSettings.model = modelName; 150 | schema.plugin(autoIncrement.plugin, settings.autoIncrementSettings); 151 | } 152 | } 153 | 154 | // If model name contains an underscore then camelCase it. 155 | var propName = modelName.charAt(0).toUpperCase() + modelName.slice(1); 156 | if (propName.indexOf('_') !== -1) { 157 | propName = propName.replace(/_(.)/g, function (match, letter) { 158 | return letter.toUpperCase(); 159 | }); 160 | } 161 | 162 | // Store model in db API. 163 | db[propName] = db.connection.model(modelName, schema); 164 | } 165 | } 166 | 167 | // Set modelsLoaded to true. 168 | db.modelsLoaded = true; 169 | 170 | // Invoke callback with resulting db object. 171 | if (settings.callback) settings.callback(null, db); 172 | }); 173 | }); 174 | 175 | // Return db object immediately in case the app would like a lazy-loaded reference. 176 | return db; 177 | }, 178 | 179 | // Expose mongoose types for easy access. 180 | Types: mongoose.Schema.Types, 181 | 182 | // Expose all of mongoose for easy access. 183 | mongoose: mongoose 184 | }; 185 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-simpledb", 3 | "version": "5.1.0", 4 | "description": "Simple API for defining mongoose dbmodels and loading them into a single object for easy access.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "extend": "^3.0.0", 8 | "mongoose-auto-increment": "^5.0.1" 9 | }, 10 | "peerDependencies": { 11 | "mongoose": "^4.0.0" 12 | }, 13 | "devDependencies": { 14 | "mocha": "*", 15 | "chai": "*", 16 | "moment": "*", 17 | "marked": "*", 18 | "async": "*" 19 | }, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/codetunnel/mongoose-simpledb.git" 26 | }, 27 | "keywords": [ 28 | "mongoose" 29 | ], 30 | "author": "Alex Ford", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/codetunnel/mongoose-simpledb/issues" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/dbmodels/Author.js: -------------------------------------------------------------------------------- 1 | var moment = require('moment'), 2 | marked = require('marked'); 3 | 4 | exports.schema = { 5 | name: { 6 | first: String, 7 | last: String 8 | }, 9 | birthday: Date, 10 | gender: String, 11 | bio: String, 12 | updateCounter: {type: Number, default: 0} 13 | }; 14 | 15 | 16 | exports.pre = { 17 | save: function (next) { 18 | this.updateCounter++; 19 | next(); 20 | } 21 | }; 22 | 23 | exports.post = { 24 | save: function (record) { 25 | console.log("Post-hook is works!"); 26 | } 27 | }; 28 | 29 | 30 | exports.methods = { 31 | formatBirthday: function (formatString) { 32 | return moment(this.birthday).format(formatString); 33 | }, 34 | bioHtml: function () { 35 | return marked(this.bio); 36 | } 37 | }; 38 | 39 | exports.virtuals = { 40 | age: { 41 | get: function () { 42 | var today = new Date(); 43 | var age = today.getFullYear() - this.birthday.getFullYear(); 44 | var m = today.getMonth() - this.birthday.getMonth(); 45 | if (m < 0 || (m === 0 && today.getDate() < this.birthday.getDate())) { 46 | age--; 47 | } 48 | return age; 49 | } 50 | }, 51 | "name.full": { 52 | get: function () { 53 | return this.name.first + ' ' + this.name.last; 54 | }, 55 | set: function (fullName) { 56 | if (fullName.indexOf(' ') !== -1) { 57 | var splitName = fullName.split(' '); 58 | this.name.first = splitName[0]; 59 | this.name.last = splitName[0]; 60 | } 61 | else 62 | this.name.first = fullName; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/dbmodels/Book.js: -------------------------------------------------------------------------------- 1 | var ObjectId = require('mongoose').Schema.Types.ObjectId, 2 | async = require('async'), 3 | moment = require('moment'); 4 | 5 | exports.schema = { 6 | _id: Number, 7 | title: String, 8 | publishDate: { type: Date, default: Date.now() }, 9 | author: { type: ObjectId, ref: 'Author' } 10 | }; 11 | 12 | exports.methods = { 13 | formatPublishDate: function (formatString) { 14 | return moment(this.publishDate).format(formatString); 15 | } 16 | }; 17 | 18 | exports.statics = { 19 | findByAuthor: function (firstName, lastName, cb) { 20 | var query = { "name.first": firstName }, 21 | self = this; 22 | if (lastName) query["name.last"] = lastName; 23 | self.model('Author').find(query, function (err, authors) { 24 | if (err) return cb(err); 25 | var queries = []; 26 | authors.forEach(function (author) { 27 | queries.push(function (cb) { 28 | self.model('Book').find({ author: author._id }, cb); 29 | }); 30 | }); 31 | async.parallel(queries, function (err, results) { 32 | if (err) return cb(err); 33 | var allBooks = []; 34 | results.forEach(function (books) { 35 | allBooks.concat(books); 36 | }); 37 | cb(null, allBooks); 38 | }) 39 | }); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /test/errormodels/WrongSchema.js: -------------------------------------------------------------------------------- 1 | schema = { 2 | _id: Number 3 | }; -------------------------------------------------------------------------------- /test/errormodels2/WrongPlugin.js: -------------------------------------------------------------------------------- 1 | exports.schema = { 2 | _id: Number 3 | }; 4 | 5 | exports.plugins = [ {plugim: require('mongoose-auto-increment'), options: 'WrongPlugin'} ]; -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -R spec -------------------------------------------------------------------------------- /test/pluginsmodels/Plugged.js: -------------------------------------------------------------------------------- 1 | exports.schema = { 2 | id: Number, 3 | url: String 4 | }; 5 | 6 | exports.plugins = [ 7 | { 8 | plugin: require('mongoose-auto-increment').plugin, 9 | options: {model:'Plugged', startAt:5, field: 'id'} 10 | } 11 | ]; 12 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var should = require('chai').should(); 2 | var simpledb = require('..'); 3 | var async = require('async'); 4 | var extend = require('extend'); 5 | var path = require('path'); 6 | var options = { 7 | connectionString: 'mongodb://localhost/mongoose-simpledb-test', 8 | modelsDir: path.join(__dirname, 'dbmodels') 9 | }; 10 | 11 | after(function (done) { 12 | simpledb.init(options, function (err, db) { 13 | if (err) return done(err); 14 | setTimeout(function () { 15 | db.connection.db.dropDatabase(function (err) { 16 | if (err) return done(err); 17 | db.connection.close(done); 18 | }); 19 | }, 1000); 20 | }); 21 | }); 22 | 23 | describe("simpledb", function () { 24 | 25 | describe("init", function () { 26 | 27 | it("should invoke our callback and pass in a db object with our models loaded.", function (done) { 28 | simpledb.init(options, function (err, db) { 29 | should.not.exist(err); 30 | should.exist(db); 31 | db.should.have.property('modelsLoaded', true); 32 | done(); 33 | }); 34 | }); 35 | 36 | it("should return a db object that is lazy-loaded with our models.", function (done) { 37 | var db = simpledb.init(options, function (err) { 38 | should.not.exist(err); 39 | db.should.have.property('modelsLoaded', true); 40 | done(); 41 | }); 42 | should.exist(db); 43 | db.should.have.property('modelsLoaded', false); 44 | }); 45 | 46 | it("should allow us to pass in just a connection", function (done) { 47 | simpledb.init(options.connectionString, function (err) { 48 | // We aren't specifying a models directory and the default models directory doesn't exist so this 49 | // should fail with a "readdir" error. 50 | should.exist(err); 51 | ['ENOENT, readdir', 'ENOENT, scandir', 'ENOENT: no such'].should.include(err.message.substr(0, 15)); 52 | done(); 53 | }); 54 | }); 55 | 56 | it("should still invoke our callback if it is passed as the first and only argument.", function (done) { 57 | simpledb.init(function (err) { 58 | // We aren't specifying a models directory and the default models directory doesn't exist so this 59 | // should fail with a "readdir" error. 60 | should.exist(err); 61 | ['ENOENT, readdir', 'ENOENT, scandir', 'ENOENT: no such'].should.include(err.message.substr(0, 15)); 62 | done(); 63 | }); 64 | }); 65 | 66 | it("should not use autoincrement plugin.", function (done) { 67 | var localOptions = extend({}, options, { autoIncrementNumberIds: false }); 68 | simpledb.init(localOptions, function (err, db) { 69 | should.not.exist(err); 70 | should.exist(db); 71 | should.not.exist(db.Book.nextCount); 72 | done(); 73 | }); 74 | }); 75 | 76 | 77 | it("should load plugins.", function (done) { 78 | var localOptions = extend({}, options, { modelsDir: path.join(__dirname, 'pluginsmodels'), autoIncrementNumberIds: false}); 79 | simpledb.init(localOptions, function (err, db) { 80 | should.not.exist(err); 81 | should.exist(db); 82 | should.exist(db.Plugged.nextCount); 83 | done(); 84 | }); 85 | }); 86 | 87 | 88 | it("should throw error with model error.", function (done) { 89 | var localOptions = extend({}, options, {modelsDir: path.join(__dirname, 'errormodels')}); 90 | simpledb.init(localOptions, function (err, db) { 91 | should.exist(err); 92 | should.not.exist(db); 93 | done(); 94 | }); 95 | }); 96 | 97 | 98 | it("should throw error with plugin error.", function (done) { 99 | var localOptions = extend({}, options, {modelsDir: path.join(__dirname, 'errormodels2')}); 100 | simpledb.init(localOptions, function (err, db) { 101 | should.exist(err); 102 | should.not.exist(db); 103 | done(); 104 | }); 105 | }); 106 | 107 | 108 | }); 109 | 110 | describe("db object", function () { 111 | 112 | var _db; 113 | 114 | before(function (done) { 115 | simpledb.init(options, function (err, db) { 116 | if (err) return done(err); 117 | _db = db; 118 | done(); 119 | }); 120 | }); 121 | 122 | it("should load all dbmodels and attach them to a single object.", function () { 123 | _db.should.have.property('Book').with.property('modelName', 'Book'); 124 | _db.should.have.property('Author').with.property('modelName', 'Author'); 125 | }); 126 | 127 | it("should attach any static methods to the model.", function () { 128 | _db.Book.should.have.property('findByAuthor').and.be.a('function'); 129 | }); 130 | 131 | it("should attach any instance methods to the model.", function () { 132 | // Arrange 133 | var book = new _db.Book(); 134 | var author = new _db.Author(); 135 | 136 | // Assert 137 | book.should.have.property('formatPublishDate').and.be.a('function'); 138 | author.should.have.property('formatBirthday').and.be.a('function'); 139 | author.should.have.property('bioHtml').and.be.a('function'); 140 | }); 141 | 142 | it("should attach any virtual properties to the model.", function () { 143 | // Arrange 144 | var author = new _db.Author({ 145 | name: { 146 | first: 'Alex', 147 | last: 'Ford' 148 | }, 149 | birthday: new Date('3/2/1987') 150 | }); 151 | 152 | // Assert 153 | author.should.have.property('name').with.property('full', 'Alex Ford'); 154 | }); 155 | 156 | it("should use mongoose-auto-increment plugin if _id is set to type \"Number\"", function (done) { 157 | 158 | _db.connection.db.dropDatabase(function (err) { 159 | 160 | if (err) return done(err); 161 | 162 | simpledb.init(options, function (err, db) { 163 | 164 | if (err) return done(err); 165 | 166 | // Arrange 167 | var book = new db.Book({ 168 | title: "The Hobbit", 169 | author: new db.Author({ name: { first: "J. R. R.", last: "Tolkien" } }), 170 | publishDate: new Date("9/21/1937") 171 | }); 172 | 173 | // Act 174 | async.series({ 175 | book: function (cb) { 176 | book.save(cb); 177 | }, 178 | nextCount: function (cb) { 179 | book.nextCount(cb); 180 | } 181 | }, assert); 182 | 183 | // Assert 184 | function assert(err, results) { 185 | should.not.exist(err); 186 | results.book[0].should.have.property('_id', 0); 187 | results.nextCount.should.equal(1); 188 | done(); 189 | } 190 | 191 | }); 192 | 193 | }); 194 | 195 | }); 196 | 197 | it("should correct work with pre and post hooks", function (done) { 198 | 199 | simpledb.init(options, function (err, db) { 200 | 201 | var author = new db.Author({ 202 | name: { 203 | first: 'Alex', 204 | last: 'Ford' 205 | }, 206 | birthday: new Date('3/2/1987') 207 | }); 208 | author.updateCounter.should.equal(0); 209 | author.save(); 210 | 211 | db.Author.findOne(function (err, record) { 212 | record.updateCounter.should.equal(1); 213 | done(); 214 | }); 215 | 216 | }); 217 | 218 | 219 | }); 220 | 221 | it("should correct set settings of mongoose-auto-increment plugin", function (done) { 222 | 223 | var localOptions = extend({}, options, { modelsDir: path.join(__dirname, 'pluginsmodels'), autoIncrementSettings: { startAt: 5, field: 'id' } }); 224 | 225 | _db.connection.db.dropDatabase(function (err) { 226 | 227 | if (err) return done(err); 228 | 229 | simpledb.init(localOptions, function (err, db) { 230 | 231 | if (err) return done(err); 232 | 233 | var ulrs = new db.Plugged({ 234 | url: "https://github.com" 235 | }); 236 | 237 | // Act 238 | async.series({ 239 | ulrs: function (cb) { 240 | ulrs.save(cb); 241 | }, 242 | nextCount: function (cb) { 243 | ulrs.nextCount(cb); 244 | } 245 | }, assert); 246 | 247 | // Assert 248 | function assert(err, results) { 249 | should.not.exist(err); 250 | results.ulrs[0].should.have.property('id', 5); 251 | results.nextCount.should.equal(6); 252 | done(); 253 | } 254 | 255 | }); 256 | 257 | }); 258 | 259 | }); 260 | 261 | }); 262 | 263 | }); 264 | --------------------------------------------------------------------------------