11

I'm having a massive headache try to share some common schema definitions via a module to all the other modules in my code base.

I have a myproj_schemas module that contains these two schemas:

var mongoose = require('mongoose'),
    util = require("util"),
    Schema = mongoose.Schema;

var BaseProfileSchema = function() {
    Schema.apply(this, arguments);

    this.add({
        _user: {type: Schema.Types.ObjectId, ref: 'User', required: true},
        name: {type: String, required: true},
        bio: {type: String, required: true},
        pictureLink: String
    });

};
util.inherits(BaseProfileSchema, Schema);

module.exports = BaseProfileSchema;

And

var mongoose = require('mongoose'),
    BaseProfileSchema = require('./base_profile_schema.js'),
    Schema = mongoose.Schema;

var entSchemaAdditions = {
    mentors: {type: Schema.Types.ObjectId, ref: 'Mentor'}
};


var entrepreneurSchema = new BaseProfileSchema(entSchemaAdditions);

module.exports = entrepreneurSchema;

Mentors is also defined in another file.

My unit tests for these both work in the schemas module.

When I npm install this module and try to create using

Entrepreneur = db.model('Entrepreneur', entrepreneurSchema),

I get the following error:

TypeError: Undefined type at paths.mentors Did you try nesting Schemas? You can only nest using refs or arrays.

If I use the same code in my local module, then no problem. If I reference the schema file directly in the require (e.g. require('../node_modules/myproj_schemas/models/ent_schema') then I get the error.

I'm fairly sure it wasn't breaking like this earlier, but I've backed out all the changes and it is still not working.

I'm drawing a complete blank, and any suggestions would be gratefully received.

EDIT:

I've created a new Schemas module. It has one Schema:

var mongoose = require('mongoose');

var userSchema = new mongoose.Schema({
    email: String
});

module.exports = userSchema;

This also fails when packaged up in a module and npm install'd to other modules.

Running on OS X

JonRed
  • 2,853
  • 5
  • 28
  • 37

3 Answers3

9

Personally I created separate "common" project with an init method to register all of the models with mongodb, and call the init method in the app.js file of any apps that need access to the models.

  1. Create a shared project - Create a new node project, following the standard process.
  2. package.json - In the shared project, set your package.json file to contain the following entry:

    "main": "index.js"
    
  3. Add a model - Create a new models folder within your shared project, to contain all of your mongoose schemas and plugins. Add your userSchema file to the models folder and name it user.js.

    var mongoose = require('mongoose');
    
    var userSchema = new mongoose.Schema({
        email: String
    });
    
    module.exports = mongoose.model('User', userSchema);
    
  4. index.js - Then in the project's root index.js file create a shared object that can be used by your apps, exposing the models and an init method. There's many ways to code this, but here's how I'm doing it:

    function Common() {
        //empty array to hold mongoose Schemas
        this.models = {};
    }
    
    Common.prototype.init = function(mongoose) {
        mongoose.connect('your mongodb connection string goes here');
        require('./models/user');
        //add more model references here
    
        //This is just to make referencing the models easier within your apps, so you don't have to use strings. The model name you use here must match the name you used in the schema file
        this.models = {
            user: mongoose.model('User')
        }
    }
    
    var common = new Common();
    
    module.exports = common;
    
  5. Reference your common project - However you want to reference your shared project, add the reference to the shared project in the package.json file within your app and give it a name of common. Personally I used GitHub to store the project and referenced the repository path. Since my repository was private I had to use a key in the path, which is covered on the GitHub support site.
  6. Init the models in your app - In the start script for your app (let's assume it's app.js for this example) add the reference to your common project and call the init method to connect to the mongodb server and register the models.

    //at the top, near your other module dependencies
    var mongoose = require('mongoose')
      , common = require('common');
    
    common.init(mongoose);
    
  7. Use the model anywhere in your app - Now that mongoose has the connection pool established and the models have been registered, you can use the models is any of the classes within your app. For example, say you have a page that displays information about a user you could do it like this (untested code, just wrote this for an example):

    var common = require('common');
    
    app.get('/user-profile/:id', function(req, res) {
        common.models.user.findById(req.params.id, function(err, user) {
             if (err)
                 console.log(err.message); //do something else to handle the error
             else
                 res.render('user-profile', {model: {user: user}});
        });
    });
    

EDIT Sorry, I didn't see the line where you were inheriting one schema from another. As one of the other answers stated, mongoose already offers the concept of a plugin. In your example above, you would do this:

In your common module, under '/models/base-profile-plugin.js'

module.exports = exports = function baseProfilePlugin(schema, options){

    //These paths will be added to any schema that uses this plugin
    schema.add({
        _user: {type: Schema.Types.ObjectId, ref: 'User', required: true},
        name: {type: String, required: true},
        bio: {type: String, required: true},
        pictureLink: String
    });

    //you can also add static or instance methods or shared getter/setter handling logic here. See the plugin documentation on the mongoose website.
}

In your common module, under '/models/entrepreneur.js

var mongoose    = require('mongoose')
  , basePlugin  = require('./base-profile-plugin.js');

var entrepreneurSchema   = new mongoose.Schema({
    mentors: {type: Schema.Types.ObjectId, ref: 'Mentor'}
});

entrepreneurSchema.plugin(basePlugin);

module.exports = mongoose.model('Entrepreneur', entrepreneurSchema);
Brian Shamblen
  • 4,653
  • 1
  • 23
  • 37
  • Thanks for such a complete answer. Since having this problem, I'm moved more to this type of solution which is not requiring me to share schemas between modules. I still don't feel I have clear idea as to why the problem occurred, but I'm beginning to think it was environmental, and maybe some cache clearing would have helped. – JonRed Nov 20 '14 at 17:37
  • I'm going to accept this answer - not because it actually answers my question directly, but because I think it will serve the community most by having it at the top. Again, thanks for your effort. – JonRed Nov 20 '14 at 17:38
  • I didn't really get this to work, and had to use `proxyquire` to overwrite the Mongoose instance used in my schema definition modules. Works, but seems very dirty. – Mikke Jan 28 '15 at 10:59
  • 2
    I don't know if this worked back then, but today, if you pass in a "mongoose" module and do ".connect()" to it, the connection will be local to that module. So the models you defined in the models directory, are requiring another "mongoose", but this one is not connected. So models will not be available on the mongoose you passed in on "init". – dciccale Oct 10 '16 at 10:23
  • 1
    @tdecs I was wondering this as well. I've come to this thread and was thinking it was really useful for me too but I got the "MissingSchemaError: Schema hasn't been registered for model" error. Did you found a solution? – eraffaelli Feb 13 '18 at 21:15
1

Why don't you create a mongoose plugin for common schema like this https://github.com/Keeguon/mongoose-behaviors/blob/master/lib/timestampable.js

1

what you are looking for basically is for schema inheritance, there's a project named mongoose extend that practically solves your issue, you can either decide if you want to implement it or take a look to the code and make your own.

just install it usng npm

$ npm install mongoose-schema-extend

this is how it works:

var mongoose = require('mongoose'),
    extend = require('mongoose-schema-extend');
var Schema = mongoose.Schema;

var PersonSchema = new Schema({
  name : String
}, { collection : 'users' });

var EmployeeSchema = PersonSchema.extend({
  department : String
});

var Person = mongoose.model('Person', PersonSchema),
    Employee = mongoose.model('Employee', EmployeeSchema);

var Brian = new Employee({
  name : 'Brian Kirchoff',
  department : 'Engineering'
});

regards

pedrommuller
  • 15,741
  • 10
  • 76
  • 126
  • This is a really helpful answer, but doesn't address the issue. In the edit you can see I had the issue with a non-extended schema. I'm more and more convinced that this problem was environmental, but not had the chance to prove it on another machine yet. – JonRed Nov 20 '14 at 17:32