From 21fdcb59732541430c6ba2b0f45a6a0dd06dd591 Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Sat, 28 Mar 2015 22:30:35 -0400 Subject: [PATCH 1/9] wip --- lib/casts/hasAndBelongsToMany.js | 88 +++++++++++++++++++--------- lib/casts/hasMany.js | 3 +- lib/index.js | 97 +------------------------------ specs/hasAndBelongsToMany.spec.js | 48 +++++++++++---- 4 files changed, 103 insertions(+), 133 deletions(-) diff --git a/lib/casts/hasAndBelongsToMany.js b/lib/casts/hasAndBelongsToMany.js index b77fd2f..fc98363 100644 --- a/lib/casts/hasAndBelongsToMany.js +++ b/lib/casts/hasAndBelongsToMany.js @@ -1,37 +1,71 @@ -var ObjectId = require('mongoose').Schema.ObjectId, - pluralize = require('../utils').pluralize; +var mongoose = require('mongoose') + , i = require('i')(); -module.exports = function hasAndBelongsToMany (schema, model, options) { - this.type = 'habtm'; +function HasAndBelongsToManyGetter (val, schemaType){ + return val; +}; + +HasAndBelongsToManyGetter.prototype.build = function (object) { +}; - this.schema = schema; - this.model = model; - this.options = options || {}; +module.exports = function (schema, childAssociationName, options) { + options = options || {}; + options.childAssociationName = childAssociationName; + options.childModelName = childModelName = i.classify(childAssociationName); - this.pathName = this.options.through || pluralize(this.model.toLowerCase()); + //var get = new HasAndBelongsToMany(this, options); var path = {}; - path[this.pathName] = [{ type: ObjectId, index: true, ref: this.model }]; - this.schema.add(path); + path[childAssociationName] = [ + { + type: mongoose.Schema.Types.ObjectId + , ref: childModelName + , index: true + , get: HasAndBelongsToManyGetter + } + ] - this.schema.paths[this.pathName].options[this.type] = this.model; - this.schema.paths[this.pathName].options.relationshipType = this.type; - this.schema.paths[this.pathName].options.relationshipModel = this.model; + schema.add(path); - if (this.options.dependent) { - this.schema.paths[this.pathName].options.dependent = this.options.dependent; - } +}; - var setChild = this.options.hasOwnProperty('setChild') ? this.options.setChild : true; - this.schema.paths[this.pathName].options.setChild = setChild; - if (!this.schema.paths[this.pathName].options.setChild) { - if (this.schema.paths[this.pathName].options.dependent == 'nullify') { - throw new Error("dependent cannot be set to 'nullify' while setChild is false"); - } - if (this.schema.paths[this.pathName].options.dependent == 'destroy') { - throw new Error("dependent cannot be set to 'destroy' while setChild is false"); - } - }; -}; + + + + + + + //this.type = 'habtm'; + + //this.schema = schema; + //this.model = model; + //this.options = options || {}; + + //this.pathName = this.options.through || pluralize(this.model.toLowerCase()); + + //var path = {}; + //path[this.pathName] = [{ type: ObjectId, index: true, ref: this.model }]; + //this.schema.add(path); + + //this.schema.paths[this.pathName].options[this.type] = this.model; + //this.schema.paths[this.pathName].options.relationshipType = this.type; + //this.schema.paths[this.pathName].options.relationshipModel = this.model; + + //if (this.options.dependent) { + //this.schema.paths[this.pathName].options.dependent = this.options.dependent; + //} + + //var setChild = this.options.hasOwnProperty('setChild') ? this.options.setChild : true; + //this.schema.paths[this.pathName].options.setChild = setChild; + + //if (!this.schema.paths[this.pathName].options.setChild) { + //if (this.schema.paths[this.pathName].options.dependent == 'nullify') { + //throw new Error("dependent cannot be set to 'nullify' while setChild is false"); + //} + + //if (this.schema.paths[this.pathName].options.dependent == 'destroy') { + //throw new Error("dependent cannot be set to 'destroy' while setChild is false"); + //} + //}; diff --git a/lib/casts/hasMany.js b/lib/casts/hasMany.js index a939ba4..b74d7d6 100644 --- a/lib/casts/hasMany.js +++ b/lib/casts/hasMany.js @@ -49,8 +49,7 @@ function associate (objects) { }; function HasMany (doc, options) { - options = options || {}; - this._options = options; + this._options = options || {}; this.as = this._options.as; this.inverse_of = this.inverse_of; diff --git a/lib/index.js b/lib/index.js index 611159c..aec076b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,19 +1,9 @@ var mongoose = require('mongoose'), Schema = mongoose.Schema, - MongooseArray = mongoose.Types.Array, belongsTo = require('./casts/belongsTo'), hasAndBelongsToMany = require('./casts/hasAndBelongsToMany'), - hasMany = require('./casts/hasMany'), - Relationship = require('./relationship'); + hasMany = require('./casts/hasMany'); -/** - * Syntactic sugar to create the relationships - * - * @param {String} model [name of the model in the DB] - * @param {Object} options [through, dependent] - * @return {Schema} - * @api public - */ Schema.prototype.belongsTo = function (model, options) { belongsTo(this, model, options); }; @@ -23,88 +13,7 @@ Schema.prototype.hasMany = function (model, options) { }; Schema.prototype.habtm = function (model, options) { - new hasAndBelongsToMany(this, model, options); + hasAndBelongsToMany(this, model, options); }; -/** - * Builds the instance of the child element - * - * @param {Object|Array} objs - * @return {Document|Array} - * @api public - */ -MongooseArray.prototype.build = function (objs) { - return (new Relationship(this)).build(objs); -}; - -/** - * Create a child document and add it to the parent `Array` - * - * @param {Object|Array} objs [object(s) to create] - * @param {Functions} callback [passed: (err, parent, created children)] - * @api public - */ -MongooseArray.prototype.create = function (objs, callback) { - return (new Relationship(this)).create(objs, callback); -}; - -/** - * Find children documents - * - * *This is a copy of Model.find w/ added error throwing and such* - */ -MongooseArray.prototype.find = function (conditions, fields, options, callback) { - return (new Relationship(this)).find(conditions, fields, options, callback); -}; - -/** - * Syntactic sugar to populate the array - * - * @param {Array} fields - * @param {Function} callback - * @return {Query} - * @api public - */ -MongooseArray.prototype.populate = function (fields, callback) { - return (new Relationship(this)).populate(fields, callback); -} - -/** - * Append an already instantiated document - * saves it in the process. - * - * @param {Document} child - * @param {Function} callback - * @api public - */ -MongooseArray.prototype.append = function (child, callback) { - return (new Relationship(this)).append(child, callback); -}; - - -/** - * Append many instantiated children documents - * - * @param {Array} children - * @param {Function} callback - * @api public - */ - -MongooseArray.prototype._concat = MongooseArray.prototype.concat; -MongooseArray.prototype.concat = function (children, callback) { - return (new Relationship(this)).concat(children, callback); -}; - -/** - * Overrides MongooseArray.remove - * only for dependent:destroy relationships - * - * @param {ObjectId} id - * @param {Function} callback - * @return {ObjectId} - * @api public - */ -MongooseArray.prototype._remove = MongooseArray.prototype.remove; -MongooseArray.prototype.remove = MongooseArray.prototype.delete = function (id, callback) { - return (new Relationship(this)).delete(id, callback); -}; +Schema.prototype.hasAndBelongsToMany = Schema.prototype.habtm; diff --git a/specs/hasAndBelongsToMany.spec.js b/specs/hasAndBelongsToMany.spec.js index 9b2e683..f0b36b4 100644 --- a/specs/hasAndBelongsToMany.spec.js +++ b/specs/hasAndBelongsToMany.spec.js @@ -1,16 +1,44 @@ require('./spec_helper'); var mongoose = require('mongoose'), - should = require('should'), - TwitterUser = require('./support/twitterUserModel'), - Pet = require('./support/petModel') - Dog = require('./support/dogModel') - Fish = require('./support/fishModel') - TwitterPost = require('./support/twitterPostModel'), - Category = require('./support/categoryModel'), - Tweet = require('./support/tweetModel'), - Tag = require('./support/tagModel'), - BookSchema = new mongoose.Schema({}); + should = require('should'); + +describe.only('hasManyBelongsToMany default options', function() { + var paintingSchema, Painting, painting + , colorSchema, Color, color; + + before(function(){ + paintingSchema = new mongoose.Schema({ title: String }); + paintingSchema.hasAndBelongsToMany('colors'); + Painting = mongoose.model('Painting', paintingSchema); + + colorSchema = new mongoose.Schema({ name: String }); + colorSchema.habtm('paintings'); + Color = mongoose.model('Color', colorSchema); + }); + + it.skip('sets virtuals for the relationships', function(){ + should(colorSchema.virtuals.paintings).be.an.instanceOf(mongoose.VirtualType); + should(paintingSchema.virtuals.colors).be.an.instanceOf(mongoose.VirtualType); + }); + + it.skip('sets schema for the relationships', function(){ + console.log(colorSchema.paths.paintings); + }); + + describe('#build', function(){ + it('initializes a new document with the appropriate association', function(){ + console.log(colorSchema.paths.paintings); + var painting = new Painting({ title: 'Mona Lisa' }); + console.log(painting.colors); + var color = painting.colors.build({ name: 'Black' }); + + //should(color.paintings).include(painting._id); + + }); + + }); +}); describe('hasManyBelongsToMany', function() { describe('valid options', function() { From ffe054036effe57873c3d52befb205fbd9498914 Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Sun, 29 Mar 2015 16:05:00 -0400 Subject: [PATCH 2/9] wtf --- lib/casts/hasAndBelongsToMany.js | 26 ++------ lib/index.js | 95 ++++----------------------- lib/types/HasAndBelongsToManyArray.js | 11 ++++ specs/hasAndBelongsToMany.spec.js | 32 ++++----- 4 files changed, 43 insertions(+), 121 deletions(-) create mode 100644 lib/types/HasAndBelongsToManyArray.js diff --git a/lib/casts/hasAndBelongsToMany.js b/lib/casts/hasAndBelongsToMany.js index fc98363..fbdd64f 100644 --- a/lib/casts/hasAndBelongsToMany.js +++ b/lib/casts/hasAndBelongsToMany.js @@ -1,32 +1,20 @@ var mongoose = require('mongoose') - , i = require('i')(); - -function HasAndBelongsToManyGetter (val, schemaType){ - return val; -}; - -HasAndBelongsToManyGetter.prototype.build = function (object) { -}; + , HasAndBelongsToManyArray = mongoose.Types.HasAndBelongsToManyArray + , i = require('i')(); module.exports = function (schema, childAssociationName, options) { options = options || {}; options.childAssociationName = childAssociationName; options.childModelName = childModelName = i.classify(childAssociationName); - //var get = new HasAndBelongsToMany(this, options); - var path = {}; - path[childAssociationName] = [ - { - type: mongoose.Schema.Types.ObjectId - , ref: childModelName - , index: true - , get: HasAndBelongsToManyGetter - } - ] + path[childAssociationName] = [{ + type: mongoose.Schema.Types.ObjectId + , ref: childModelName + , index: true + }]; schema.add(path); - }; diff --git a/lib/index.js b/lib/index.js index 7868a62..4d14166 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,19 +2,18 @@ module.exports = exports = function mongoRelations (mongoose) { var Schema = mongoose.Schema - , MongooseArray = mongoose.Types.Array - , belongsTo = require('./casts/belongsTo') - , hasAndBelongsToMany = require('./casts/hasAndBelongsToMany') - , hasMany = require('./casts/hasMany') - , Relationship = require('./relationship'); + , habtmArrayFactory = require('./types/HasAndBelongsToManyArray') + , belongsTo + , hasAndBelongsToMany + , hasMany; + + // register HasAndBelongsToMany Type + mongoose.Types.HasAndBelongsToManyArray = require('./types/HasAndBelongsToManyArray')(mongoose.Types.Array) + + belongsTo = require('./casts/belongsTo'); + hasAndBelongsToMany = require('./casts/hasAndBelongsToMany'); + hasMany = require('./casts/hasMany'); - /* Syntactic sugar to create the relationships - * - * @param {String} model [name of the model in the DB] - * @param {Object} options [through, dependent] - * @return {Schema} - * @api public - */ Schema.prototype.belongsTo = function (model, options) { belongsTo(this, model, options); }; @@ -28,77 +27,5 @@ module.exports = exports = function mongoRelations (mongoose) { }; Schema.prototype.hasAndBelongsToMany = Schema.prototype.habtm; - /* Builds the instance of the child element - * - * @param {Object|Array} objs - * @return {Document|Array} - * @api public - */ - MongooseArray.prototype.build = function (objs) { - return (new Relationship(this)).build(objs); - }; - - /* Create a child document and add it to the parent `Array` - * - * @param {Object|Array} objs [object(s) to create] - * @param {Functions} callback [passed: (err, parent, created children)] - * @api public - */ - MongooseArray.prototype.create = function (objs, callback) { - return (new Relationship(this)).create(objs, callback); - }; - - /* Find children documents - * - * *This is a copy of Model.find w/ added error throwing and such* - */ - MongooseArray.prototype.find = function (conditions, fields, options, callback) { - return (new Relationship(this)).find(conditions, fields, options, callback); - }; - - /* Syntactic sugar to populate the array - * - * @param {Array} fields - * @param {Function} callback - * @return {Query} - * @api public - */ - MongooseArray.prototype.populate = function (fields, callback) { - return (new Relationship(this)).populate(fields, callback); - } - - /* Append an already instantiated document saves it in the process. - * - * @param {Document} child - * @param {Function} callback - * @api public - */ - MongooseArray.prototype.append = function (child, callback) { - return (new Relationship(this)).append(child, callback); - }; - - /* Append many instantiated children documents - * - * @param {Array} children - * @param {Function} callback - * @api public - */ - MongooseArray.prototype._concat = MongooseArray.prototype.concat; - MongooseArray.prototype.concat = function (children, callback) { - return (new Relationship(this)).concat(children, callback); - }; - - /* Overrides MongooseArray.remove only for dependent:destroy relationships - * - * @param {ObjectId} id - * @param {Function} callback - * @return {ObjectId} - * @api public - */ - MongooseArray.prototype._remove = MongooseArray.prototype.remove; - MongooseArray.prototype.remove = MongooseArray.prototype.delete = function (id, callback) { - return (new Relationship(this)).delete(id, callback); - }; - return mongoose; }; diff --git a/lib/types/HasAndBelongsToManyArray.js b/lib/types/HasAndBelongsToManyArray.js new file mode 100644 index 0000000..f256aef --- /dev/null +++ b/lib/types/HasAndBelongsToManyArray.js @@ -0,0 +1,11 @@ +module.exports = exports = function (MongooseArray) { + var HasAndBelongsToManyArray; + + function HasAndBelongsToManyArray ( ) { + MongooseArray.apply(this, arguments); + }; + HasAndBelongsToManyArray.prototype = Object.create(MongooseArray.prototype); + HasAndBelongsToManyArray.prototype.build = function(){}; + + return HasAndBelongsToManyArray; +}; diff --git a/specs/hasAndBelongsToMany.spec.js b/specs/hasAndBelongsToMany.spec.js index f0b36b4..e83f7b3 100644 --- a/specs/hasAndBelongsToMany.spec.js +++ b/specs/hasAndBelongsToMany.spec.js @@ -1,7 +1,7 @@ require('./spec_helper'); -var mongoose = require('mongoose'), - should = require('should'); +var mongoose = require('mongoose'), + should = require('should'); describe.only('hasManyBelongsToMany default options', function() { var paintingSchema, Painting, painting @@ -17,26 +17,22 @@ describe.only('hasManyBelongsToMany default options', function() { Color = mongoose.model('Color', colorSchema); }); - it.skip('sets virtuals for the relationships', function(){ - should(colorSchema.virtuals.paintings).be.an.instanceOf(mongoose.VirtualType); - should(paintingSchema.virtuals.colors).be.an.instanceOf(mongoose.VirtualType); - }); - - it.skip('sets schema for the relationships', function(){ - console.log(colorSchema.paths.paintings); - }); - describe('#build', function(){ - it('initializes a new document with the appropriate association', function(){ - console.log(colorSchema.paths.paintings); - var painting = new Painting({ title: 'Mona Lisa' }); - console.log(painting.colors); - var color = painting.colors.build({ name: 'Black' }); + it('initializes a new document with the appropriate association', function(done){ + painting = new Painting({ title: 'Mona Lisa' }); + color = new Color({ name: 'Black' }); + console.log(painting.colors.build); + painting.colors.push(color); + painting.save(function(err){ + Painting.findById(painting._id, function(err, painting){ + console.log(painting.colors); + done(); + }); + }); + //color = painting.colors.build({ name: 'Black' }); //should(color.paintings).include(painting._id); - }); - }); }); From 3268c360b2fa87fa49df2a7ea8718ac3de519398 Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Mon, 30 Mar 2015 08:10:57 -0400 Subject: [PATCH 3/9] changes --- lib/casts/hasAndBelongsToMany.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/casts/hasAndBelongsToMany.js b/lib/casts/hasAndBelongsToMany.js index fbdd64f..15ab7ab 100644 --- a/lib/casts/hasAndBelongsToMany.js +++ b/lib/casts/hasAndBelongsToMany.js @@ -12,9 +12,13 @@ module.exports = function (schema, childAssociationName, options) { type: mongoose.Schema.Types.ObjectId , ref: childModelName , index: true + //, get: function(){ console.log(arguments); } }]; schema.add(path); + console.log('-'); + console.log(schema.paths[childAssociationName]); + }; From 73835d140c776b9eb8fde894cc4fae7a7d8b4a3b Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Mon, 30 Mar 2015 18:23:59 -0400 Subject: [PATCH 4/9] wip --- lib/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/index.js b/lib/index.js index 4d14166..6435396 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,9 +10,9 @@ module.exports = exports = function mongoRelations (mongoose) { // register HasAndBelongsToMany Type mongoose.Types.HasAndBelongsToManyArray = require('./types/HasAndBelongsToManyArray')(mongoose.Types.Array) - belongsTo = require('./casts/belongsTo'); - hasAndBelongsToMany = require('./casts/hasAndBelongsToMany'); - hasMany = require('./casts/hasMany'); + belongsTo = require('./belongsTo'); + hasAndBelongsToMany = require('./hasAndBelongsToMany'); + hasMany = require('./hasMany'); Schema.prototype.belongsTo = function (model, options) { belongsTo(this, model, options); From e01c33da2faba08c1b107fdd4aefac95c3217140 Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Mon, 30 Mar 2015 18:29:55 -0400 Subject: [PATCH 5/9] wip --- specs/hasAndBelongsToMany.spec.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/specs/hasAndBelongsToMany.spec.js b/specs/hasAndBelongsToMany.spec.js index e83f7b3..d382fff 100644 --- a/specs/hasAndBelongsToMany.spec.js +++ b/specs/hasAndBelongsToMany.spec.js @@ -1,9 +1,18 @@ require('./spec_helper'); -var mongoose = require('mongoose'), - should = require('should'); - -describe.only('hasManyBelongsToMany default options', function() { +var mongoose = require('mongoose') + , should = require('should') + , TwitterUser = require('./support/twitterUserModel') + , Pet = require('./support/petModel') + , Dog = require('./support/dogModel') + , Fish = require('./support/fishModel') + , TwitterPost = require('./support/twitterPostModel') + , Category = require('./support/categoryModel') + , Tweet = require('./support/tweetModel') + , Tag = require('./support/tagModel') + , BookSchema = new mongoose.Schema({}); + +describe.skip('hasManyBelongsToMany default options', function() { var paintingSchema, Painting, painting , colorSchema, Color, color; From b0e2e8e1a8d39983c2e50a60a029413d2e868a9c Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Mon, 30 Mar 2015 18:50:48 -0400 Subject: [PATCH 6/9] wip --- .../relationship/hasAndBelongsToMany.spec.js | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 specs/relationship/hasAndBelongsToMany.spec.js diff --git a/specs/relationship/hasAndBelongsToMany.spec.js b/specs/relationship/hasAndBelongsToMany.spec.js deleted file mode 100644 index a2bb92f..0000000 --- a/specs/relationship/hasAndBelongsToMany.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -require('../spec_helper'); - -var should = require('should') - , Relationship = require('../../lib/relationship/hasAndBelongsToMany') - , User = require('../support/userModel') - , Pet = require('../support/petModel') - , Dog = require('../support/dogModel') - , Fish = require('../support/fishModel') - , Post = require('../support/postModel') - , Category = require('../support/categoryModel'); - -describe('hasManyBelongsToMany', function() { - describe('constructor', function() { - var path, relationship, user; - - beforeEach(function(){ - user = new User(); - relationship = new Relationship(user.pets); - }); - - it("holds reference to it's path", function(){ - should(relationship._path).eql(user.pets); - }); - - it('caches allowed discriminators', function(){ - should(relationship._allowed_discriminators.sort()).eql(['Pet', 'Dog', 'Fish'].sort()); - }); - - it('knows the relationship of the child to the parent', function(){ - var definition = relationship._childToParent; - should(definition.name).eql('users'); - should(definition.relationshipModel).eql('User'); - should(definition.relationshipType).eql('habtm'); - }); - - it('caches a reference to the parent of the relationship', function(){ - should(relationship._parent.constructor.modelName).eql('User'); - should(relationship._parentModelName).eql('User'); - }); - - it('caches the name of the child base model', function(){ - should(relationship._childModelName).eql('Pet'); - }); - }); -}); From 6c78a856a833335277e35fce7f7aedd3b608676e Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Mon, 30 Mar 2015 19:04:25 -0400 Subject: [PATCH 7/9] wip --- lib/hasAndBelongsToMany.js | 8 ++++---- specs/support/categoryModel.js | 2 +- specs/support/petSchemaBase.js | 2 +- specs/support/tweetModel.js | 2 +- specs/support/twitterPostModel.js | 3 +-- specs/support/twitterUserModel.js | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/hasAndBelongsToMany.js b/lib/hasAndBelongsToMany.js index 6650fb2..709806d 100644 --- a/lib/hasAndBelongsToMany.js +++ b/lib/hasAndBelongsToMany.js @@ -5,14 +5,14 @@ var mongoose = require('mongoose') , utils = require('./utils') , merge = utils.merge; -module.exports = function hasAndBelongsToMany (schema, model, options) { - this.type = 'habtm'; +module.exports = function hasAndBelongsToMany (schema, associationName, options) { + this.type = 'habtm'; this.schema = schema; - this.model = model; this.options = options || {}; - this.pathName = this.options.through || i.pluralize(this.model.toLowerCase()); + this.pathName = associationName; + this.model = this.options.modelName || i.classify(associationName); var path = {}; path[this.pathName] = [{ type: ObjectId, index: true, ref: this.model }]; diff --git a/specs/support/categoryModel.js b/specs/support/categoryModel.js index 0d80fa2..382c011 100644 --- a/specs/support/categoryModel.js +++ b/specs/support/categoryModel.js @@ -7,7 +7,7 @@ var CategorySchema = new mongoose.Schema({ CategorySchema.belongsTo('twitter_user', { through: 'editor' }); // should only delete the reference -CategorySchema.habtm('TwitterPost', { through: 'posts', dependent: 'delete' }); +CategorySchema.habtm('posts', { modelName: 'TwitterPost', dependent: 'delete' }); CategorySchema.hasMany('pets'); diff --git a/specs/support/petSchemaBase.js b/specs/support/petSchemaBase.js index 71a5edc..2e59e3c 100644 --- a/specs/support/petSchemaBase.js +++ b/specs/support/petSchemaBase.js @@ -15,7 +15,7 @@ function PetSchemaBase() { Schema.apply(this, arguments); - this.habtm('TwitterUser', { setParent: false }); + this.habtm('twitter_users', { setParent: false }); this.belongsTo('category'); } util.inherits(PetSchemaBase, Schema); diff --git a/specs/support/tweetModel.js b/specs/support/tweetModel.js index 6183555..231755b 100644 --- a/specs/support/tweetModel.js +++ b/specs/support/tweetModel.js @@ -3,6 +3,6 @@ var mongoose = require('mongoose'); var tweetSchema = new mongoose.Schema({ title: String, body: String }); tweetSchema.belongsTo('author', { modelName: 'TwitterUser', required: true }); -tweetSchema.habtm('Tag', { through: 'tags', setChild: false }) +tweetSchema.habtm('tags', { setChild: false }) module.exports = mongoose.model('Tweet', tweetSchema); diff --git a/specs/support/twitterPostModel.js b/specs/support/twitterPostModel.js index 3396b58..842835e 100644 --- a/specs/support/twitterPostModel.js +++ b/specs/support/twitterPostModel.js @@ -6,7 +6,6 @@ var twitterPostSchema = new mongoose.Schema({ twitterPostSchema.belongsTo('author', { modelName: 'TwitterUser' }); -// should not delete the reference -twitterPostSchema.habtm('Category'); +twitterPostSchema.habtm('categories'); module.exports = mongoose.model('TwitterPost', twitterPostSchema); diff --git a/specs/support/twitterUserModel.js b/specs/support/twitterUserModel.js index d7b9413..decd6bd 100644 --- a/specs/support/twitterUserModel.js +++ b/specs/support/twitterUserModel.js @@ -6,6 +6,6 @@ twitterUserSchema.hasMany('categories'); twitterUserSchema.hasMany('tags', { dependent: 'nullify' }); twitterUserSchema.hasMany('tweets', { dependent: 'delete', inverse_of: 'author' }); -twitterUserSchema.habtm('Pet', { setParent: false }) +twitterUserSchema.habtm('pets', { setParent: false }) module.exports = mongoose.model('TwitterUser', twitterUserSchema); From 25bca6f7cc73d1f03286f8fbd639b81ebd80a928 Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Tue, 5 May 2015 17:05:01 -0400 Subject: [PATCH 8/9] wip --- lib/hasAndBelongsToMany.js | 675 ++++++++++++++++-------------- specs/hasAndBelongsToMany.spec.js | 29 +- 2 files changed, 389 insertions(+), 315 deletions(-) diff --git a/lib/hasAndBelongsToMany.js b/lib/hasAndBelongsToMany.js index 709806d..3f865e2 100644 --- a/lib/hasAndBelongsToMany.js +++ b/lib/hasAndBelongsToMany.js @@ -3,43 +3,112 @@ var mongoose = require('mongoose') , MongooseArray = mongoose.Types.Array , i = require('i')() , utils = require('./utils') - , merge = utils.merge; + , merge = utils.merge + , HasAndBelongsToMany + , associate; -module.exports = function hasAndBelongsToMany (schema, associationName, options) { +function associate (object) { + objects[this.foreignKey] = this.doc._id; + return object; +}; - this.type = 'habtm'; - this.schema = schema; - this.options = options || {}; +function HasMany (doc, options) { + this._options = options || {}; - this.pathName = associationName; - this.model = this.options.modelName || i.classify(associationName); + this.as = this._options.as; + this.inverse_of = this.inverse_of; - var path = {}; - path[this.pathName] = [{ type: ObjectId, index: true, ref: this.model }]; - this.schema.add(path); + this.doc = doc; + this.modelName = this.doc.constructor.modelName; + this.Model = mongoose.model(this.modelName); + + this.associationModelName = this._options.associationModelName; + this.associationModel = mongoose.model(this.associationModelName); + + if(this.as){ + this.foreignKey = this.as; + this.foreignKeyType = this.as + '_type'; + } + else { + this.foreignKey = this.inverse_of || i.underscore(this.modelName); + } +} + +function HasAndBelongsToMany (doc, options) { + this._options = options || {}; + + this.as = this._options.as; + this.inverse_of = this.inverse_of; + + this.doc = doc; + this.modelName = this.doc.constructor.modelName; + this.Model = mongoose.model(this.modelName); - this.schema.paths[this.pathName].options[this.type] = this.model; - this.schema.paths[this.pathName].options.relationshipType = this.type; - this.schema.paths[this.pathName].options.relationshipModel = this.model; + this.associationModelName = this._options.associationModelName; + this.associationModel = mongoose.model(this.associationModelName); - if (this.options.dependent) { - this.schema.paths[this.pathName].options.dependent = this.options.dependent; + if(this.as){ + this.foreignKey = this.as; + this.foreignKeyType = this.as + '_type'; } + else { + this.foreignKey = this.inverse_of || i.underscore(this.modelName); + } +} + +var HasAndBelongsToManyMongooseArray; +function HasAndBelongsToManyMongooseArray () { +} - var setChild = this.options.hasOwnProperty('setChild') ? this.options.setChild : true; - this.schema.paths[this.pathName].options.setChild = setChild; +module.exports = function (schema, associationName, options) { + options = options || {}; + options.associationName = associationName; + options.associationModelName = associationModelName = i.classify(associationName); - if (!this.schema.paths[this.pathName].options.setChild) { - if (this.schema.paths[this.pathName].options.dependent == 'nullify') { - throw new Error("dependent cannot be set to 'nullify' while setChild is false"); - } + var path = {}; + path[associationName] = new HasAndBelongsToManyMongooseArray[{ + type: ObjectId, + index: true, + ref: associationModelName + }]; - if (this.schema.paths[this.pathName].options.dependent == 'destroy') { - throw new Error("dependent cannot be set to 'destroy' while setChild is false"); - } - }; + this.schema.add(path); }; +//module.exports = function hasAndBelongsToMany (schema, associationName, options) { + //this.type = 'habtm'; + //this.schema = schema; + //this.options = options || {}; + + //this.pathName = associationName; + //this.model = this.options.modelName || i.classify(associationName); + + //var path = {}; + //path[this.pathName] = [{ type: ObjectId, index: true, ref: this.model }]; + //this.schema.add(path); + + //this.schema.paths[this.pathName].options[this.type] = this.model; + //this.schema.paths[this.pathName].options.relationshipType = this.type; + //this.schema.paths[this.pathName].options.relationshipModel = this.model; + + //if (this.options.dependent) { + //this.schema.paths[this.pathName].options.dependent = this.options.dependent; + //} + + //var setChild = this.options.hasOwnProperty('setChild') ? this.options.setChild : true; + //this.schema.paths[this.pathName].options.setChild = setChild; + + //if (!this.schema.paths[this.pathName].options.setChild) { + //if (this.schema.paths[this.pathName].options.dependent == 'nullify') { + //throw new Error("dependent cannot be set to 'nullify' while setChild is false"); + //} + + //if (this.schema.paths[this.pathName].options.dependent == 'destroy') { + //throw new Error("dependent cannot be set to 'destroy' while setChild is false"); + //} + //}; +//}; + /* Builds the instance of the child element * * @param {Object|Array} objs @@ -47,42 +116,42 @@ module.exports = function hasAndBelongsToMany (schema, associationName, options) * @api public */ -MongooseArray.prototype.build = function (objs) { - var childModelName = this._schema.options.relationshipModel; +//MongooseArray.prototype.build = function (objs) { + //var childModelName = this._schema.options.relationshipModel; - var buildOne = function(obj){ - var childModel = mongoose.model(obj.__t || childModelName) - , child = new childModel(obj); + //var buildOne = function(obj){ + //var childModel = mongoose.model(obj.__t || childModelName) + //, child = new childModel(obj); - this._parent[this._path].push(child); + //this._parent[this._path].push(child); - if (!!this._schema.options.setChild) { + //if (!!this._schema.options.setChild) { - // start remove me asap - // needed for this._childToParent.name - model = mongoose.model(this._schema.options.relationshipModel); - for (var path in model.schema.paths) { - options = model.schema.paths[path].options; - ref = (options.relationshipModel || options.ref); - if(ref == this._parent.constructor.modelName){ - options.name = path; - this._childToParent = options - } - } - // end remove me asap + //// start remove me asap + //// needed for this._childToParent.name + //model = mongoose.model(this._schema.options.relationshipModel); + //for (var path in model.schema.paths) { + //options = model.schema.paths[path].options; + //ref = (options.relationshipModel || options.ref); + //if(ref == this._parent.constructor.modelName){ + //options.name = path; + //this._childToParent = options + //} + //} + //// end remove me asap - child[this._childToParent.name].push(this._parent); - } + //child[this._childToParent.name].push(this._parent); + //} - return child; - }.bind(this); + //return child; + //}.bind(this); - if (Array.isArray(objs)) { - return objs.map(buildOne); - } else { - return buildOne(objs); - } -}; + //if (Array.isArray(objs)) { + //return objs.map(buildOne); + //} else { + //return buildOne(objs); + //} +//}; /* Create a child document and add it to the parent `Array` * @@ -91,65 +160,65 @@ MongooseArray.prototype.build = function (objs) { * @api public */ -MongooseArray.prototype.create = function (objs, callback) { - objs = this.build(objs); - - var complete = function(err, docs){ - this._parent.save(function(err){ - callback(err, this._parent, docs); - }.bind(this)); - }.bind(this); - - var validForCreate = function(doc){ - if (!!this._schema.options.setChild) { - - // start remove me asap - // needed for this._childToParent.name - model = mongoose.model(this._schema.options.relationshipModel); - - for (var path in model.schema.paths) { - options = model.schema.paths[path].options; - ref = (options.relationshipModel || options.ref); - if(ref == this._parent.constructor.modelName){ - options.name = path; - this._childToParent = options - } - } - - this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); - var childIsAllowed = function (child) { - return !!~this._allowed_discriminators.indexOf(child.constructor.modelName); - }.bind(this); - - // end remove me asap - - return !!this._childToParent && childIsAllowed(doc); - } else { - return true - } - }.bind(this); - - var createOne = function(doc, done){ - if (!validForCreate(doc)) - return done(new Error('Parent model not referenced anywhere in the Child Schema')); - doc.save(done); - }; - - if(Array.isArray(objs)){ - var count = objs.length, docs = []; - - objs.forEach(function(obj){ - createOne(obj, function(err, doc){ - if (err) return complete(err); - docs.push(doc); - --count || complete(null, docs); - }.bind(this)); - }.bind(this)); - } - else { - createOne(objs, complete); - } -}; +//MongooseArray.prototype.create = function (objs, callback) { + //objs = this.build(objs); + + //var complete = function(err, docs){ + //this._parent.save(function(err){ + //callback(err, this._parent, docs); + //}.bind(this)); + //}.bind(this); + + //var validForCreate = function(doc){ + //if (!!this._schema.options.setChild) { + + //// start remove me asap + //// needed for this._childToParent.name + //model = mongoose.model(this._schema.options.relationshipModel); + + //for (var path in model.schema.paths) { + //options = model.schema.paths[path].options; + //ref = (options.relationshipModel || options.ref); + //if(ref == this._parent.constructor.modelName){ + //options.name = path; + //this._childToParent = options + //} + //} + + //this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); + //var childIsAllowed = function (child) { + //return !!~this._allowed_discriminators.indexOf(child.constructor.modelName); + //}.bind(this); + + //// end remove me asap + + //return !!this._childToParent && childIsAllowed(doc); + //} else { + //return true + //} + //}.bind(this); + + //var createOne = function(doc, done){ + //if (!validForCreate(doc)) + //return done(new Error('Parent model not referenced anywhere in the Child Schema')); + //doc.save(done); + //}; + + //if(Array.isArray(objs)){ + //var count = objs.length, docs = []; + + //objs.forEach(function(obj){ + //createOne(obj, function(err, doc){ + //if (err) return complete(err); + //docs.push(doc); + //--count || complete(null, docs); + //}.bind(this)); + //}.bind(this)); + //} + //else { + //createOne(objs, complete); + //} +//}; /* Append an already instantiated document saves it in the process. * @@ -157,42 +226,42 @@ MongooseArray.prototype.create = function (objs, callback) { * @param {Function} callback * @api public */ -MongooseArray.prototype.append = function (child, callback) { - - // start remove me asap - // needed for this._childToParent.name - model = mongoose.model(this._schema.options.relationshipModel); - - for (var path in model.schema.paths) { - options = model.schema.paths[path].options; - ref = (options.relationshipModel || options.ref); - if(ref == this._parent.constructor.modelName){ - options.name = path; - this._childToParent = options - } - } +//MongooseArray.prototype.append = function (child, callback) { - this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); - var childIsAllowed = function (child) { - return !!~this._allowed_discriminators.indexOf(child.constructor.modelName); - }.bind(this); + //// start remove me asap + //// needed for this._childToParent.name + //model = mongoose.model(this._schema.options.relationshipModel); - // end remove me asap + //for (var path in model.schema.paths) { + //options = model.schema.paths[path].options; + //ref = (options.relationshipModel || options.ref); + //if(ref == this._parent.constructor.modelName){ + //options.name = path; + //this._childToParent = options + //} + //} - // TODO: abstract me - if(!childIsAllowed(child)) { - return throwErr('Wrong Model type'); - } + //this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); + //var childIsAllowed = function (child) { + //return !!~this._allowed_discriminators.indexOf(child.constructor.modelName); + //}.bind(this); - if (!!this._schema.options.setChild) { - child[this._childToParent.name].push(this._parent._id); - } + //// end remove me asap - this._parent[this._path].push(child._id); + //// TODO: abstract me + //if(!childIsAllowed(child)) { + //return throwErr('Wrong Model type'); + //} - callback && child.save(callback); - return child; -}; + //if (!!this._schema.options.setChild) { + //child[this._childToParent.name].push(this._parent._id); + //} + + //this._parent[this._path].push(child._id); + + //callback && child.save(callback); + //return child; +//}; /* Append many instantiated children documents * @@ -200,95 +269,95 @@ MongooseArray.prototype.append = function (child, callback) { * @param {Function} callback * @api public */ -MongooseArray.prototype._concat = MongooseArray.prototype.concat; -MongooseArray.prototype.concat = function (docs, callback) { - var throwErr = utils.throwErr(callback); +//MongooseArray.prototype._concat = MongooseArray.prototype.concat; +//MongooseArray.prototype.concat = function (docs, callback) { + //var throwErr = utils.throwErr(callback); - if (!Array.isArray(docs)){ - return throwErr('First argument needs to be an Array'); - }; + //if (!Array.isArray(docs)){ + //return throwErr('First argument needs to be an Array'); + //}; - var complete = function(err, docs) { - if(err){ return throwErr(err) } + //var complete = function(err, docs) { + //if(err){ return throwErr(err) } - var ids = docs.map(function (doc) { return doc._id }); - this._concat(ids); - this._markModified(); + //var ids = docs.map(function (doc) { return doc._id }); + //this._concat(ids); + //this._markModified(); - callback(null, docs); - }.bind(this); + //callback(null, docs); + //}.bind(this); - var count = docs.length; - var savedDocs = []; - docs.forEach(function(doc){ - this.append(doc); - doc.save(function(err, doc){ - if(err) return complete(err); + //var count = docs.length; + //var savedDocs = []; + //docs.forEach(function(doc){ + //this.append(doc); + //doc.save(function(err, doc){ + //if(err) return complete(err); - savedDocs.push(doc); - --count || complete(null, savedDocs); - }); - }.bind(this)); + //savedDocs.push(doc); + //--count || complete(null, savedDocs); + //}); + //}.bind(this)); -}; +//}; /* Find children documents * * *This is a copy of Model.find w/ added error throwing and such* */ -MongooseArray.prototype.find = function (conditions, fields, options, callback) { - // Copied from `Model.find` - if ('function' == typeof conditions) { - callback = conditions; - conditions = {}; - fields = null; - options = null; - } else if ('function' == typeof fields) { - callback = fields; - fields = null; - options = null; - } else if ('function' == typeof options) { - callback = options; - options = null; - } - - // start remove me asap - // needed for this._childToParent.name - model = mongoose.model(this._schema.options.relationshipModel); - for (var path in model.schema.paths) { - options = model.schema.paths[path].options; - ref = (options.relationshipModel || options.ref); - if(ref == this._parent.constructor.modelName){ - options.name = path; - this._childToParent = options - } - } - // end remove me asap - - var childModel = mongoose.model(this._schema.options.relationshipModel); - childPath = this._childToParent; - safeConditions = {}, - throwErr = utils.throwErr(callback); - - merge(safeConditions, conditions); - - if (!!this._schema.options.setChild) { - if (!childPath) { - return throwErr('Parent model not referenced anywhere in the Child Schema'); - } - - var childConditions = {}; - childConditions[childPath.name] = this._parent._id; - merge(safeConditions, childConditions); - } - - merge(safeConditions, { _id: { $in: this._parent[this._path] } }); - - var query = childModel.find(safeConditions, options).select(fields); - - callback && query.exec(callback); - return query; -}; +//MongooseArray.prototype.find = function (conditions, fields, options, callback) { + //// Copied from `Model.find` + //if ('function' == typeof conditions) { + //callback = conditions; + //conditions = {}; + //fields = null; + //options = null; + //} else if ('function' == typeof fields) { + //callback = fields; + //fields = null; + //options = null; + //} else if ('function' == typeof options) { + //callback = options; + //options = null; + //} + + //// start remove me asap + //// needed for this._childToParent.name + //model = mongoose.model(this._schema.options.relationshipModel); + //for (var path in model.schema.paths) { + //options = model.schema.paths[path].options; + //ref = (options.relationshipModel || options.ref); + //if(ref == this._parent.constructor.modelName){ + //options.name = path; + //this._childToParent = options + //} + //} + //// end remove me asap + + //var childModel = mongoose.model(this._schema.options.relationshipModel); + //childPath = this._childToParent; + //safeConditions = {}, + //throwErr = utils.throwErr(callback); + + //merge(safeConditions, conditions); + + //if (!!this._schema.options.setChild) { + //if (!childPath) { + //return throwErr('Parent model not referenced anywhere in the Child Schema'); + //} + + //var childConditions = {}; + //childConditions[childPath.name] = this._parent._id; + //merge(safeConditions, childConditions); + //} + + //merge(safeConditions, { _id: { $in: this._parent[this._path] } }); + + //var query = childModel.find(safeConditions, options).select(fields); + + //callback && query.exec(callback); + //return query; +//}; /* Syntactic sugar to populate the array * @@ -297,18 +366,18 @@ MongooseArray.prototype.find = function (conditions, fields, options, callback) * @return {Query} * @api public */ -MongooseArray.prototype.populate = function (fields, callback) { - if ('function' == typeof fields) { - callback = fields; - fields = null; - } - - // TODO: do we really need to initialize a new doc? - return this._parent.constructor - .findById(this._parent._id) - .populate(this._path, fields) - .exec(callback); -}; +//MongooseArray.prototype.populate = function (fields, callback) { + //if ('function' == typeof fields) { + //callback = fields; + //fields = null; + //} + + //// TODO: do we really need to initialize a new doc? + //return this._parent.constructor + //.findById(this._parent._id) + //.populate(this._path, fields) + //.exec(callback); +//}; /* Overrides MongooseArray.remove only for dependent:destroy relationships * @@ -317,77 +386,77 @@ MongooseArray.prototype.populate = function (fields, callback) { * @return {ObjectId} * @api public */ -MongooseArray.prototype._remove = MongooseArray.prototype.remove; -MongooseArray.prototype.remove = MongooseArray.prototype.delete = function (id, callback) { - var parent = this._parent, - childModel = mongoose.model(this._schema.options.relationshipModel); - childPath = this._childToParent; - child = null, - throwErr = utils.throwErr(callback); - - if (id._id) { - var child = id; - id = child._id; - } - - // TODO: should a callback be required? - if (!callback) { - callback = function (err) { - if (err) { - throw err; - } - }; - } - - var hasOrFetchChild = function(done){ - if(child){ - done(null, child); - } else { - childModel.findById(id, done); - }; - }; - - // TODO: is this needed? - // I think this removing the id from the instance array - // however, it could be not needed - MongooseArray.prototype._remove.call(this, id); - - // TODO: shold habtm support delete and destroy? - if (!!~['delete', 'destroy', 'nullify'].indexOf(this._schema.options.dependent)){ - hasOrFetchChild(function(err, child){ - if (err) { return throwErr(err) }; - child[childPath.name].remove(parent._id); - child.save(function(err, child){ - if (err){ return throwErr(err) }; - callback(null, parent); - }); - }); - } else { - callback(null, parent); - } -}; +//MongooseArray.prototype._remove = MongooseArray.prototype.remove; +//MongooseArray.prototype.remove = MongooseArray.prototype.delete = function (id, callback) { + //var parent = this._parent, + //childModel = mongoose.model(this._schema.options.relationshipModel); + //childPath = this._childToParent; + //child = null, + //throwErr = utils.throwErr(callback); + + //if (id._id) { + //var child = id; + //id = child._id; + //} + + //// TODO: should a callback be required? + //if (!callback) { + //callback = function (err) { + //if (err) { + //throw err; + //} + //}; + //} + + //var hasOrFetchChild = function(done){ + //if(child){ + //done(null, child); + //} else { + //childModel.findById(id, done); + //}; + //}; + + //// TODO: is this needed? + //// I think this removing the id from the instance array + //// however, it could be not needed + //MongooseArray.prototype._remove.call(this, id); + + //// TODO: shold habtm support delete and destroy? + //if (!!~['delete', 'destroy', 'nullify'].indexOf(this._schema.options.dependent)){ + //hasOrFetchChild(function(err, child){ + //if (err) { return throwErr(err) }; + //child[childPath.name].remove(parent._id); + //child.save(function(err, child){ + //if (err){ return throwErr(err) }; + //callback(null, parent); + //}); + //}); + //} else { + //callback(null, parent); + //} +//}; // has and belongs to many -function HasAndBelongsToMany (path) { - var ref, options, model; - - this.type = 'habtm'; - this._path = path; - this._parent = path._parent; - this._options = path._schema.options; - this._childModelName = this._options.relationshipModel; - this._parentModelName = this._parent.constructor.modelName; - - model = mongoose.model(this._options.relationshipModel); - this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); - - for (var path in model.schema.paths) { - options = model.schema.paths[path].options; - ref = (options.relationshipModel || options.ref); - if(ref == this._parentModelName){ - options.name = path; - this._childToParent = options - } - } -} +//function HasAndBelongsToMany (path) { + //var ref, options, model; + + //this.type = 'habtm'; + //this._path = path; + //this._parent = path._parent; + //this._options = path._schema.options; + //this._childModelName = this._options.relationshipModel; + //this._parentModelName = this._parent.constructor.modelName; + + //model = mongoose.model(this._options.relationshipModel); + //this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); + + //for (var path in model.schema.paths) { + //options = model.schema.paths[path].options; + //ref = (options.relationshipModel || options.ref); + //if(ref == this._parentModelName){ + //options.name = path; + //this._childToParent = options + //} + //} +//} diff --git a/specs/hasAndBelongsToMany.spec.js b/specs/hasAndBelongsToMany.spec.js index d382fff..a9cd100 100644 --- a/specs/hasAndBelongsToMany.spec.js +++ b/specs/hasAndBelongsToMany.spec.js @@ -12,7 +12,7 @@ var mongoose = require('mongoose') , Tag = require('./support/tagModel') , BookSchema = new mongoose.Schema({}); -describe.skip('hasManyBelongsToMany default options', function() { +describe.only('hasManyBelongsToMany without options', function() { var paintingSchema, Painting, painting , colorSchema, Color, color; @@ -27,20 +27,25 @@ describe.skip('hasManyBelongsToMany default options', function() { }); describe('#build', function(){ + it('initializes a new document with the appropriate association', function(){ + painting = new Painting({ title: 'Mona Lisa' }); + color = painting.colors.build({ name: 'Black' }); + + should(color.paintings).containEql(painting._id); + should(painting.colors).containEql(color._id); + }); + }); + + describe('#create', function(){ it('initializes a new document with the appropriate association', function(done){ painting = new Painting({ title: 'Mona Lisa' }); - color = new Color({ name: 'Black' }); - console.log(painting.colors.build); - painting.colors.push(color); - painting.save(function(err){ - Painting.findById(painting._id, function(err, painting){ - console.log(painting.colors); - done(); - }); - }); - //color = painting.colors.build({ name: 'Black' }); + painting.colors.create({ name: 'Black' }, function(err, _, color){ + should(color.paintings).containEql(painting._id); + should(color.isNew).be.false; + should(painting.colors).containEql(color._id); - //should(color.paintings).include(painting._id); + done(); + }); }); }); }); From d3da4c27d2628fdffe23694c09807d5008aad29f Mon Sep 17 00:00:00 2001 From: Jonathon Storer Date: Tue, 23 Jun 2015 11:27:28 -0400 Subject: [PATCH 9/9] wip --- lib/hasAndBelongsToMany.js | 788 ++++++++++++++---------------- lib/hasMany.js | 2 - specs/hasAndBelongsToMany.spec.js | 22 +- specs/hasMany.spec.js | 6 +- 4 files changed, 373 insertions(+), 445 deletions(-) diff --git a/lib/hasAndBelongsToMany.js b/lib/hasAndBelongsToMany.js index 3f865e2..fe01677 100644 --- a/lib/hasAndBelongsToMany.js +++ b/lib/hasAndBelongsToMany.js @@ -7,456 +7,386 @@ var mongoose = require('mongoose') , HasAndBelongsToMany , associate; -function associate (object) { - objects[this.foreignKey] = this.doc._id; - return object; -}; +module.exports = function hasAndBelongsToMany (schema, associationName, options) { + this.type = 'habtm'; + this.schema = schema; + this.options = options || {}; + + this.pathName = associationName; + this.model = this.options.modelName || i.classify(associationName); + + var path = {}; + path[this.pathName] = [{ type: ObjectId, index: true, ref: this.model }]; + this.schema.add(path); + + this.schema.paths[this.pathName].options[this.type] = this.model; + this.schema.paths[this.pathName].options.relationshipType = this.type; + this.schema.paths[this.pathName].options.relationshipModel = this.model; -function HasMany (doc, options) { - this._options = options || {}; + if (this.options.dependent) { + this.schema.paths[this.pathName].options.dependent = this.options.dependent; + } - this.as = this._options.as; - this.inverse_of = this.inverse_of; + var setChild = this.options.hasOwnProperty('setChild') ? this.options.setChild : true; + this.schema.paths[this.pathName].options.setChild = setChild; - this.doc = doc; - this.modelName = this.doc.constructor.modelName; - this.Model = mongoose.model(this.modelName); + if (!this.schema.paths[this.pathName].options.setChild) { + if (this.schema.paths[this.pathName].options.dependent == 'nullify') { + throw new Error("dependent cannot be set to 'nullify' while setChild is false"); + } - this.associationModelName = this._options.associationModelName; - this.associationModel = mongoose.model(this.associationModelName); + if (this.schema.paths[this.pathName].options.dependent == 'destroy') { + throw new Error("dependent cannot be set to 'destroy' while setChild is false"); + } + }; +}; - if(this.as){ - this.foreignKey = this.as; - this.foreignKeyType = this.as + '_type'; +// Builds the instance of the child element +// +// @param {Object|Array} objs +// @return {Document|Array} +// @api public + +MongooseArray.prototype.build = function (objs) { + var childModelName = this._schema.options.relationshipModel; + + var buildOne = function(obj){ + var childModel = mongoose.model(obj.__t || childModelName) + , child = new childModel(obj); + + this._parent[this._path].push(child); + + if (!!this._schema.options.setChild) { + + // start remove me asap + // needed for this._childToParent.name + model = mongoose.model(this._schema.options.relationshipModel); + for (var path in model.schema.paths) { + options = model.schema.paths[path].options; + ref = (options.relationshipModel || options.ref); + if(ref == this._parent.constructor.modelName){ + options.name = path; + this._childToParent = options + } + } + // end remove me asap + + child[this._childToParent.name].push(this._parent); + } + + return child; + }.bind(this); + + if (Array.isArray(objs)) { + return objs.map(buildOne); + } else { + return buildOne(objs); + } +}; + +// Create a child document and add it to the parent `Array` +// +// @param {Object|Array} objs [object(s) to create] +// @param {Functions} callback [passed: (err, parent, created children)] +// @api public + +MongooseArray.prototype.create = function (objs, callback) { + objs = this.build(objs); + + var complete = function(err, docs){ + this._parent.save(function(err){ + callback(err, this._parent, docs); + }.bind(this)); + }.bind(this); + + var validForCreate = function(doc){ + if (!!this._schema.options.setChild) { + + // start remove me asap + // needed for this._childToParent.name + model = mongoose.model(this._schema.options.relationshipModel); + + for (var path in model.schema.paths) { + options = model.schema.paths[path].options; + ref = (options.relationshipModel || options.ref); + if(ref == this._parent.constructor.modelName){ + options.name = path; + this._childToParent = options + } + } + + this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); + var childIsAllowed = function (child) { + return !!~this._allowed_discriminators.indexOf(child.constructor.modelName); + }.bind(this); + + // end remove me asap + + return !!this._childToParent && childIsAllowed(doc); + } else { + return true + } + }.bind(this); + + var createOne = function(doc, done){ + if (!validForCreate(doc)) + return done(new Error('Parent model not referenced anywhere in the Child Schema')); + doc.save(done); + }; + + if(Array.isArray(objs)){ + var count = objs.length, docs = []; + + objs.forEach(function(obj){ + createOne(obj, function(err, doc){ + if (err) return complete(err); + docs.push(doc); + --count || complete(null, docs); + }.bind(this)); + }.bind(this)); } else { - this.foreignKey = this.inverse_of || i.underscore(this.modelName); + createOne(objs, complete); } -} +}; -function HasAndBelongsToMany (doc, options) { - this._options = options || {}; +// Append an already instantiated document saves it in the process. +// +// @param {Document} child +// @param {Function} callback +// @api public + +MongooseArray.prototype.append = function (child, callback) { + + // start remove me asap + // needed for this._childToParent.name + model = mongoose.model(this._schema.options.relationshipModel); + + for (var path in model.schema.paths) { + options = model.schema.paths[path].options; + ref = (options.relationshipModel || options.ref); + if(ref == this._parent.constructor.modelName){ + options.name = path; + this._childToParent = options + } + } - this.as = this._options.as; - this.inverse_of = this.inverse_of; + this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); + var childIsAllowed = function (child) { + return !!~this._allowed_discriminators.indexOf(child.constructor.modelName); + }.bind(this); - this.doc = doc; - this.modelName = this.doc.constructor.modelName; - this.Model = mongoose.model(this.modelName); + // end remove me asap - this.associationModelName = this._options.associationModelName; - this.associationModel = mongoose.model(this.associationModelName); + // TODO: abstract me + if(!childIsAllowed(child)) { + return throwErr('Wrong Model type'); + } - if(this.as){ - this.foreignKey = this.as; - this.foreignKeyType = this.as + '_type'; + if (!!this._schema.options.setChild) { + child[this._childToParent.name].push(this._parent._id); } - else { - this.foreignKey = this.inverse_of || i.underscore(this.modelName); + + this._parent[this._path].push(child._id); + + callback && child.save(callback); + return child; +}; + +// Append many instantiated children documents +// +// @param {Array} children +// @param {Function} callback +// @api public + +MongooseArray.prototype._concat = MongooseArray.prototype.concat; +MongooseArray.prototype.concat = function (docs, callback) { + var throwErr = utils.throwErr(callback); + + if (!Array.isArray(docs)){ + return throwErr('First argument needs to be an Array'); + }; + + var complete = function(err, docs) { + if(err){ return throwErr(err) } + + var ids = docs.map(function (doc) { return doc._id }); + this._concat(ids); + this._markModified(); + + callback(null, docs); + }.bind(this); + + var count = docs.length; + var savedDocs = []; + docs.forEach(function(doc){ + this.append(doc); + doc.save(function(err, doc){ + if(err) return complete(err); + + savedDocs.push(doc); + --count || complete(null, savedDocs); + }); + }.bind(this)); + +}; + +// Find children documents + +// *This is a copy of Model.find w/ added error throwing and such* + +MongooseArray.prototype.find = function (conditions, fields, options, callback) { + // Copied from `Model.find` + if ('function' == typeof conditions) { + callback = conditions; + conditions = {}; + fields = null; + options = null; + } else if ('function' == typeof fields) { + callback = fields; + fields = null; + options = null; + } else if ('function' == typeof options) { + callback = options; + options = null; } -} -var HasAndBelongsToManyMongooseArray; -function HasAndBelongsToManyMongooseArray () { -} + // start remove me asap + // needed for this._childToParent.name + model = mongoose.model(this._schema.options.relationshipModel); + for (var path in model.schema.paths) { + options = model.schema.paths[path].options; + ref = (options.relationshipModel || options.ref); + if(ref == this._parent.constructor.modelName){ + options.name = path; + this._childToParent = options + } + } + // end remove me asap -module.exports = function (schema, associationName, options) { - options = options || {}; - options.associationName = associationName; - options.associationModelName = associationModelName = i.classify(associationName); + var childModel = mongoose.model(this._schema.options.relationshipModel); + childPath = this._childToParent; + safeConditions = {}, + throwErr = utils.throwErr(callback); - var path = {}; - path[associationName] = new HasAndBelongsToManyMongooseArray[{ - type: ObjectId, - index: true, - ref: associationModelName - }]; + merge(safeConditions, conditions); - this.schema.add(path); + if (!!this._schema.options.setChild) { + if (!childPath) { + return throwErr('Parent model not referenced anywhere in the Child Schema'); + } + + var childConditions = {}; + childConditions[childPath.name] = this._parent._id; + merge(safeConditions, childConditions); + } + + merge(safeConditions, { _id: { $in: this._parent[this._path] } }); + + var query = childModel.find(safeConditions, options).select(fields); + + callback && query.exec(callback); + return query; +}; + +// Syntactic sugar to populate the array + +// @param {Array} fields +// @param {Function} callback +// @return {Query} +// @api public + +MongooseArray.prototype.populate = function (fields, callback) { + if ('function' == typeof fields) { + callback = fields; + fields = null; + } + + // TODO: do we really need to initialize a new doc? + return this._parent.constructor + .findById(this._parent._id) + .populate(this._path, fields) + .exec(callback); }; -//module.exports = function hasAndBelongsToMany (schema, associationName, options) { - //this.type = 'habtm'; - //this.schema = schema; - //this.options = options || {}; - - //this.pathName = associationName; - //this.model = this.options.modelName || i.classify(associationName); - - //var path = {}; - //path[this.pathName] = [{ type: ObjectId, index: true, ref: this.model }]; - //this.schema.add(path); - - //this.schema.paths[this.pathName].options[this.type] = this.model; - //this.schema.paths[this.pathName].options.relationshipType = this.type; - //this.schema.paths[this.pathName].options.relationshipModel = this.model; - - //if (this.options.dependent) { - //this.schema.paths[this.pathName].options.dependent = this.options.dependent; - //} - - //var setChild = this.options.hasOwnProperty('setChild') ? this.options.setChild : true; - //this.schema.paths[this.pathName].options.setChild = setChild; - - //if (!this.schema.paths[this.pathName].options.setChild) { - //if (this.schema.paths[this.pathName].options.dependent == 'nullify') { - //throw new Error("dependent cannot be set to 'nullify' while setChild is false"); - //} - - //if (this.schema.paths[this.pathName].options.dependent == 'destroy') { - //throw new Error("dependent cannot be set to 'destroy' while setChild is false"); - //} - //}; -//}; - -/* Builds the instance of the child element -* -* @param {Object|Array} objs -* @return {Document|Array} -* @api public -*/ - -//MongooseArray.prototype.build = function (objs) { - //var childModelName = this._schema.options.relationshipModel; - - //var buildOne = function(obj){ - //var childModel = mongoose.model(obj.__t || childModelName) - //, child = new childModel(obj); - - //this._parent[this._path].push(child); - - //if (!!this._schema.options.setChild) { - - //// start remove me asap - //// needed for this._childToParent.name - //model = mongoose.model(this._schema.options.relationshipModel); - //for (var path in model.schema.paths) { - //options = model.schema.paths[path].options; - //ref = (options.relationshipModel || options.ref); - //if(ref == this._parent.constructor.modelName){ - //options.name = path; - //this._childToParent = options - //} - //} - //// end remove me asap - - //child[this._childToParent.name].push(this._parent); - //} - - //return child; - //}.bind(this); - - //if (Array.isArray(objs)) { - //return objs.map(buildOne); - //} else { - //return buildOne(objs); - //} -//}; - -/* Create a child document and add it to the parent `Array` -* -* @param {Object|Array} objs [object(s) to create] -* @param {Functions} callback [passed: (err, parent, created children)] -* @api public -*/ - -//MongooseArray.prototype.create = function (objs, callback) { - //objs = this.build(objs); - - //var complete = function(err, docs){ - //this._parent.save(function(err){ - //callback(err, this._parent, docs); - //}.bind(this)); - //}.bind(this); - - //var validForCreate = function(doc){ - //if (!!this._schema.options.setChild) { - - //// start remove me asap - //// needed for this._childToParent.name - //model = mongoose.model(this._schema.options.relationshipModel); - - //for (var path in model.schema.paths) { - //options = model.schema.paths[path].options; - //ref = (options.relationshipModel || options.ref); - //if(ref == this._parent.constructor.modelName){ - //options.name = path; - //this._childToParent = options - //} - //} - - //this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); - //var childIsAllowed = function (child) { - //return !!~this._allowed_discriminators.indexOf(child.constructor.modelName); - //}.bind(this); - - //// end remove me asap - - //return !!this._childToParent && childIsAllowed(doc); - //} else { - //return true - //} - //}.bind(this); - - //var createOne = function(doc, done){ - //if (!validForCreate(doc)) - //return done(new Error('Parent model not referenced anywhere in the Child Schema')); - //doc.save(done); - //}; - - //if(Array.isArray(objs)){ - //var count = objs.length, docs = []; - - //objs.forEach(function(obj){ - //createOne(obj, function(err, doc){ - //if (err) return complete(err); - //docs.push(doc); - //--count || complete(null, docs); - //}.bind(this)); - //}.bind(this)); - //} - //else { - //createOne(objs, complete); - //} -//}; - -/* Append an already instantiated document saves it in the process. -* -* @param {Document} child -* @param {Function} callback -* @api public -*/ -//MongooseArray.prototype.append = function (child, callback) { - - //// start remove me asap - //// needed for this._childToParent.name - //model = mongoose.model(this._schema.options.relationshipModel); - - //for (var path in model.schema.paths) { - //options = model.schema.paths[path].options; - //ref = (options.relationshipModel || options.ref); - //if(ref == this._parent.constructor.modelName){ - //options.name = path; - //this._childToParent = options - //} - //} - - //this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); - //var childIsAllowed = function (child) { - //return !!~this._allowed_discriminators.indexOf(child.constructor.modelName); - //}.bind(this); - - //// end remove me asap - - //// TODO: abstract me - //if(!childIsAllowed(child)) { - //return throwErr('Wrong Model type'); - //} - - //if (!!this._schema.options.setChild) { - //child[this._childToParent.name].push(this._parent._id); - //} - - //this._parent[this._path].push(child._id); - - //callback && child.save(callback); - //return child; -//}; - -/* Append many instantiated children documents -* -* @param {Array} children -* @param {Function} callback -* @api public -*/ -//MongooseArray.prototype._concat = MongooseArray.prototype.concat; -//MongooseArray.prototype.concat = function (docs, callback) { - //var throwErr = utils.throwErr(callback); - - //if (!Array.isArray(docs)){ - //return throwErr('First argument needs to be an Array'); - //}; - - //var complete = function(err, docs) { - //if(err){ return throwErr(err) } - - //var ids = docs.map(function (doc) { return doc._id }); - //this._concat(ids); - //this._markModified(); - - //callback(null, docs); - //}.bind(this); - - //var count = docs.length; - //var savedDocs = []; - //docs.forEach(function(doc){ - //this.append(doc); - //doc.save(function(err, doc){ - //if(err) return complete(err); - - //savedDocs.push(doc); - //--count || complete(null, savedDocs); - //}); - //}.bind(this)); - -//}; - -/* Find children documents -* -* *This is a copy of Model.find w/ added error throwing and such* -*/ -//MongooseArray.prototype.find = function (conditions, fields, options, callback) { - //// Copied from `Model.find` - //if ('function' == typeof conditions) { - //callback = conditions; - //conditions = {}; - //fields = null; - //options = null; - //} else if ('function' == typeof fields) { - //callback = fields; - //fields = null; - //options = null; - //} else if ('function' == typeof options) { - //callback = options; - //options = null; - //} - - //// start remove me asap - //// needed for this._childToParent.name - //model = mongoose.model(this._schema.options.relationshipModel); - //for (var path in model.schema.paths) { - //options = model.schema.paths[path].options; - //ref = (options.relationshipModel || options.ref); - //if(ref == this._parent.constructor.modelName){ - //options.name = path; - //this._childToParent = options - //} - //} - //// end remove me asap - - //var childModel = mongoose.model(this._schema.options.relationshipModel); - //childPath = this._childToParent; - //safeConditions = {}, - //throwErr = utils.throwErr(callback); - - //merge(safeConditions, conditions); - - //if (!!this._schema.options.setChild) { - //if (!childPath) { - //return throwErr('Parent model not referenced anywhere in the Child Schema'); - //} - - //var childConditions = {}; - //childConditions[childPath.name] = this._parent._id; - //merge(safeConditions, childConditions); - //} - - //merge(safeConditions, { _id: { $in: this._parent[this._path] } }); - - //var query = childModel.find(safeConditions, options).select(fields); - - //callback && query.exec(callback); - //return query; -//}; - -/* Syntactic sugar to populate the array -* -* @param {Array} fields -* @param {Function} callback -* @return {Query} -* @api public -*/ -//MongooseArray.prototype.populate = function (fields, callback) { - //if ('function' == typeof fields) { - //callback = fields; - //fields = null; - //} - - //// TODO: do we really need to initialize a new doc? - //return this._parent.constructor - //.findById(this._parent._id) - //.populate(this._path, fields) - //.exec(callback); -//}; - -/* Overrides MongooseArray.remove only for dependent:destroy relationships -* -* @param {ObjectId} id -* @param {Function} callback -* @return {ObjectId} -* @api public -*/ -//MongooseArray.prototype._remove = MongooseArray.prototype.remove; -//MongooseArray.prototype.remove = MongooseArray.prototype.delete = function (id, callback) { - //var parent = this._parent, - //childModel = mongoose.model(this._schema.options.relationshipModel); - //childPath = this._childToParent; - //child = null, - //throwErr = utils.throwErr(callback); - - //if (id._id) { - //var child = id; - //id = child._id; - //} - - //// TODO: should a callback be required? - //if (!callback) { - //callback = function (err) { - //if (err) { - //throw err; - //} - //}; - //} - - //var hasOrFetchChild = function(done){ - //if(child){ - //done(null, child); - //} else { - //childModel.findById(id, done); - //}; - //}; - - //// TODO: is this needed? - //// I think this removing the id from the instance array - //// however, it could be not needed - //MongooseArray.prototype._remove.call(this, id); - - //// TODO: shold habtm support delete and destroy? - //if (!!~['delete', 'destroy', 'nullify'].indexOf(this._schema.options.dependent)){ - //hasOrFetchChild(function(err, child){ - //if (err) { return throwErr(err) }; - //child[childPath.name].remove(parent._id); - //child.save(function(err, child){ - //if (err){ return throwErr(err) }; - //callback(null, parent); - //}); - //}); - //} else { - //callback(null, parent); - //} -//}; +// Overrides MongooseArray.remove only for dependent:destroy relationships + +// @param {ObjectId} id +// @param {Function} callback +// @return {ObjectId} +// @api public + +MongooseArray.prototype._remove = MongooseArray.prototype.remove; +MongooseArray.prototype.remove = MongooseArray.prototype.delete = function (id, callback) { + var parent = this._parent, + childModel = mongoose.model(this._schema.options.relationshipModel); + childPath = this._childToParent; + child = null, + throwErr = utils.throwErr(callback); + + if (id._id) { + var child = id; + id = child._id; + } + + // TODO: should a callback be required? + if (!callback) { + callback = function (err) { + if (err) { + throw err; + } + }; + } + + var hasOrFetchChild = function(done){ + if(child){ + done(null, child); + } else { + childModel.findById(id, done); + }; + }; + + // TODO: is this needed? + // I think this removing the id from the instance array + // however, it could be not needed + MongooseArray.prototype._remove.call(this, id); + + // TODO: shold habtm support delete and destroy? + if (!!~['delete', 'destroy', 'nullify'].indexOf(this._schema.options.dependent)){ + hasOrFetchChild(function(err, child){ + if (err) { return throwErr(err) }; + child[childPath.name].remove(parent._id); + child.save(function(err, child){ + if (err){ return throwErr(err) }; + callback(null, parent); + }); + }); + } else { + callback(null, parent); + } +}; // has and belongs to many -//function HasAndBelongsToMany (path) { - //var ref, options, model; - - //this.type = 'habtm'; - //this._path = path; - //this._parent = path._parent; - //this._options = path._schema.options; - //this._childModelName = this._options.relationshipModel; - //this._parentModelName = this._parent.constructor.modelName; - - //model = mongoose.model(this._options.relationshipModel); - //this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); - - //for (var path in model.schema.paths) { - //options = model.schema.paths[path].options; - //ref = (options.relationshipModel || options.ref); - //if(ref == this._parentModelName){ - //options.name = path; - //this._childToParent = options - //} - //} -//} +function HasAndBelongsToMany (path) { + var ref, options, model; + + this.type = 'habtm'; + this._path = path; + this._parent = path._parent; + this._options = path._schema.options; + this._childModelName = this._options.relationshipModel; + this._parentModelName = this._parent.constructor.modelName; + + model = mongoose.model(this._options.relationshipModel); + this._allowed_discriminators = [ model.modelName ].concat(Object.keys(model.discriminators || {})); + + for (var path in model.schema.paths) { + options = model.schema.paths[path].options; + ref = (options.relationshipModel || options.ref); + if(ref == this._parentModelName){ + options.name = path; + this._childToParent = options + } + } +} diff --git a/lib/hasMany.js b/lib/hasMany.js index b74d7d6..8cd6ea6 100644 --- a/lib/hasMany.js +++ b/lib/hasMany.js @@ -71,8 +71,6 @@ function HasMany (doc, options) { } HasMany.prototype.build = function(objects){ - var Model; - if(Array.isArray(objects)){ return objects.map(function(object){ return this.build(object); diff --git a/specs/hasAndBelongsToMany.spec.js b/specs/hasAndBelongsToMany.spec.js index a9cd100..4bf3724 100644 --- a/specs/hasAndBelongsToMany.spec.js +++ b/specs/hasAndBelongsToMany.spec.js @@ -3,16 +3,16 @@ require('./spec_helper'); var mongoose = require('mongoose') , should = require('should') , TwitterUser = require('./support/twitterUserModel') - , Pet = require('./support/petModel') - , Dog = require('./support/dogModel') - , Fish = require('./support/fishModel') + , Pet = require('./support/petModel') + , Dog = require('./support/dogModel') + , Fish = require('./support/fishModel') , TwitterPost = require('./support/twitterPostModel') - , Category = require('./support/categoryModel') - , Tweet = require('./support/tweetModel') - , Tag = require('./support/tagModel') - , BookSchema = new mongoose.Schema({}); + , Category = require('./support/categoryModel') + , Tweet = require('./support/tweetModel') + , Tag = require('./support/tagModel') + , BookSchema = new mongoose.Schema({}); -describe.only('hasManyBelongsToMany without options', function() { +describe('hasManyBelongsToMany without options', function() { var paintingSchema, Painting, painting , colorSchema, Color, color; @@ -27,12 +27,12 @@ describe.only('hasManyBelongsToMany without options', function() { }); describe('#build', function(){ - it('initializes a new document with the appropriate association', function(){ + it.only('initializes a new document with the appropriate association', function(){ painting = new Painting({ title: 'Mona Lisa' }); color = painting.colors.build({ name: 'Black' }); - should(color.paintings).containEql(painting._id); - should(painting.colors).containEql(color._id); + should(color.paintings, 'paintings should contain painting.id').containEql(painting._id); + should(painting.colors, 'colors should contain color.id').containEql(color._id); }); }); diff --git a/specs/hasMany.spec.js b/specs/hasMany.spec.js index a4828db..7dfc437 100644 --- a/specs/hasMany.spec.js +++ b/specs/hasMany.spec.js @@ -1,9 +1,9 @@ require('./spec_helper'); var mongoose = require('mongoose') - , async = require('async') - , should = require('should') - , uuid = require('node-uuid'); + , async = require('async') + , should = require('should') + , uuid = require('node-uuid'); describe('hasMany without options', function(){ var userSchema, User, user, widgetSchema, Widget, widget;