Mongoose MissingSchemaError: Schema hasn't been registered - node.js

Hi I am learning to use mongoose middleware.
What I want is:
When I create a user, it automatically create a userPreference document for that user.
That's why I define a 'save' middleware.
Here is my code:
const mongoose = require('mongoose')
var Schema = mongoose.Schema;
//I define the schema here
const userInfo = new Schema({
Id:{ type: String,unique:true },
key:{ type: String, unique: true },
})
const userPreference = new Schema({
Id:{ type: String,unique:true },
date:Date,
color:String
})
//This is where I define the Model UserPreference , but my code says I didn't define it?
const UserPreference = mongoose.model('userPreference',userPreference);
//Here I add the middleware
userInfo.post('save', async function() {
console.log('Middleware begins')
await this.model("UserPreference").create({Id:this.Id})
});
const UserInfo = mongoose.model('userInfo',userInfo);
//Here I connectToServer and try to create the user
connectToServer( function( err, res ) {
if (err) {console.log(err);}
createUser();
})
async function createUser(){
var Id = '12345';
await UserInfo.create({Id:Id,key:'My-key'});
}
When I run my code, the console outputs the Middleware begins (so the middleware fires successfully).UserInfo also creates successfully.
However then userPreference fails to create and I always get this error:
MissingSchemaError: Schema hasn't been registered for model >"UserPreference".Use mongoose.model(name, schema)
I double check I define UserPreference in my code, and it's also before I use that UserPreference model.
const UserPreference = mongoose.model('userPreference',userPreference);

Because you registered your model with userPreference in:
const UserPreference = mongoose.model('userPreference',userPreference)
So you need to call it in this way:
this.model("userPreference").create({Id:this.Id})
Or:
UserPreference.create({Id:this.Id})

Related

mongoose findOne() is not a function

gooModel.js
//user model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const GooSchema = new Schema(
{
goo_id: {
type: String,
required: true,
unique: true,
},
},
{ collection: 'goo_collection' }
);
module.exports = Goo = mongoose.model(
'goo',
GooSchema
);
auth_controller.js
//new user
const NewGoo = new Goo({
goo_id,
})
//function
await NewGoo.findOne({ goo })}
I am getting findOne() is not a function error. I have no idea where I have made a mistake. It creates the collection on my mongo atlas DB (but it is empty). I have exactly the same model in the different route that works. I am super clueless right now.
I think you should call findOne on the model itself, not on the instance/document which NewGoo refers to. So try changing it to:
await Goo.findOne({ goo })}

How does mongoose know what collection I am accessing?

I'm having a trouble grasping a concept in Mongoose.
I'm using MongoDB atlas, got a cluster , a database and 2 collections.
users, characters.
Through a guide I've learned that a good way to write your stuff is to have a model (I use the naming schema) as a file, importing it into your Database module/class
and using it there to perform a query...
const mongoose = require("mongoose");
const process = require("./config.env");
db = () => {
return mongoose
.connect(process.env.URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: true,
})
.then((response) => {
console.log(`Connected to Databse : ${response.connection.host}`);
})
.catch((err) => {
console.log("DB_ERROR:", err);
process.exit(1);
});
};
module.exports = db;
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
});
const User = mongoose.model("User", UserSchema);
module.exports = User;
const User = require("../schemas/User");
const db = require("../config/db");
class Database {
constructor(db, collection) {
this.db = db;
this.collection = collection;
this.User = User;
}
connect() {
return db();
}
}
module.exports = Database;
one file to handle the db connection..another file as the User schema and a third file to handle every function i might use globally...
One thing I cannot wrap my mind around is
how is the findOne() function able to locate the collection I am using without me telling it what collection i want it to search in?
is it somehow translating the
const User = mongoose.model("User", UserSchema);
line and searching for "users" as well? I just can't understand the magic behind this...
what if I want to search specifically in the characters collection...?
Mongoose uses the model name, as passed when it was created: mongoose.model("User", UserSchema), converted to lower case and with an 's' appended.
For the model User it uses the collection users by default. You can change this by explicitly specifying the collection name in the schema.

How to implement more than one collection in get() function?

My model university.js
const mongoose = require('mongoose');
const UniversitySchema = mongoose.Schema({
worldranking:String,
countryranking:String,
universityname:String,
bachelorprogram:String,
masterprogram:String,
phdprogram:String,
country:String
},{collection:'us'});
const University =module.exports = mongoose.model('University',UniversitySchema);
My route.js
const express = require('express');
const router = express.Router();
const University = require('../models/university');
//retrieving data
//router.get('/universities',(req,res,next)=>{
// University.find(function(err,universities){
// if(err)
// {
// res.json(err);
//}
// res.json(universities);
//});
//});
router.get('/usa',function(req,res,next){
University.find()
.then(function(doc){
res.json({universities:doc});
});
});
module.exports= router;
How to implement multiple collections in this get() function? I put my collection name in the model. Please help me with a solution to call multiple collections in get() function.
Here is Example to use multiple collection names for one schema:
const coordinateSchema = new Schema({
lat: String,
longt: String,
name: String
}, {collection: 'WeatherCollection'});
const windSchema = new Schema({
windGust: String,
windDirection: String,
windSpeed: String
}, {collection: 'WeatherCollection'});
//Then define discriminator field for schemas:
const baseOptions = {
discriminatorKey: '__type',
collection: 'WeatherCollection'
};
//Define base model, then define other model objects based on this model:
const Base = mongoose.model('Base', new Schema({}, baseOptions));
const CoordinateModel = Base.discriminator('CoordinateModel', coordinateSchema);
const WindModel = Base.discriminator('WindModel', windSchema);
//Query normally and you get result of specific schema you are querying:
mongoose.model('CoordinateModel').find({}).then((a)=>console.log(a));
In Short,
In mongoose you can do something like this:
var users = mongoose.model('User', loginUserSchema, 'users');
var registerUser = mongoose.model('Registered', registerUserSchema, 'users');
This two schemas will save on the 'users' collection.
For more information you can refer to the documentation: http://mongoosejs.com/docs/api.html#index_Mongoose-model or you can see the following gist it might help.
Hope this may help you. You need to modify according to your requirements.

mocha --watch and mongoose models

If I leave mocha watching for changes, every time I save a file mongoose throws the following error:
OverwriteModelError: Cannot overwrite Client model once compiled
I know that mongoose won't allow to define a model twice, but I don't know how to make it work with mocha --watch.
// client.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var clientSchema = new Schema({
secret: { type: String, required: true, unique: true },
name: String,
description: String,
grant_types: [String],
created_at: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Client', clientSchema);
And here is the test
// client-test.js
var chai = require('chai');
var chaiHttp = require('chai-http');
var mongoose = require('mongoose');
var server = require('../../app');
var Client = require('../../auth/models').Client;
var should = chai.should();
chai.use(chaiHttp);
describe('client endpoints', function() {
after(function(done) {
mongoose.connection.close();
done();
});
it('should get a single client on /auth/client/{clientId} GET', function(done) {
var clt = new Client({
name: 'my app name',
description: 'super usefull and nice app',
grant_types: ['password', 'refresh_token']
});
clt.save(function(err) {
chai.request(server)
.get('/auth/client/' + clt._id.toString())
.end(function(err, res) {
res.should.have.status(200);
res.should.be.json;
res.body.should.have.property('client_id');
res.body.should.not.have.property('secret');
res.body.should.have.property('name');
res.body.should.have.property('description');
done();
});
});
});
});
I had the same issue. My solution was to check whether the model was created/compiled yet, and if not then do so, otherwise just retrieve the model.
using mongoose.modelNames() you can get an array of the names of your models. Then use .indexOf to check if the model you want to get is in the array or not. If it is not, then compile the model, for example: mongoose.model("User", UserSchema), but if it is already defined (as is the case with mocha --watch), simply retrieve the model (don't compile it again), which you can do with for example: mongoose.connection.model("User").
This is a function which returns a function to do this checking logic, which itself returns the model (either by compiling it or just retrieving it).
const mongoose = require("mongoose");
//returns a function which returns either a compiled model, or a precompiled model
//s is a String for the model name e.g. "User", and model is the mongoose Schema
function getModel(s, model) {
return function() {
return mongoose.modelNames().indexOf(s) === -1
? mongoose.model(s, model)
: mongoose.connection.model(s);
};
}
module.exports = getModel;
This means you have to require your model a bit differently, since you are likely replacing something like this:
module.exports = mongoose.model("User", UserSchema);
which returns the model itself,
with this:
module.exports = getModel("User", UserSchema);
which returns a function to return the model, either by compiling it or just retrieving it. This means when you require the 'User' model, you would want to call the function returned by getModel:
const UserModel = require("./models/UserModel")();
I hope this helps.
Here is a simpler code for the function getModel() that George is proposing
function getModel(modelName, modelSchema) {
return mongoose.models[modelName] // Check if the model exists
? mongoose.model(modelName) // If true, only retrieve it
: mongoose.model(modelName, modelSchema) // If false, define it
}
For a larger explanation on how to define and require the model, look here
Hope this helps :)
This worked for me,
place on the top of your test file:
const mongoose = require("mongoose");
mongoose.models = {};
mongoose.modelSchemas = {};

How to 'require' a database schema in nodejs

What is the best way to require a mongoose Schema in nodejs?
Originally I had these inside the app.js file but that is getting a bit large and unwieldy with more models.
Now I want to move them into a models folder and use Model = require('./models/model') to import them into app.js
How do I get it such that Model is populated with the actual model?
(exports = mongoose.model(...) fails and gives me a blank object; exports.model = mongoose.model(...) requires me to do Model.model to access it -- neither of these are the desired behavior)
===
Edit1
So basically I have taken
var mongoose = require('mongoose');
var Schema = mongoose.Schema, ObjectId = Schema.ObjectId;
var UserSchema = new Schema({
username: String,
password: String,
first_name: String,
last_name: String,
email: String
});
User = mongoose.model('User', UserSchema);
and put it into ./models/user.js
How do I get it such that its the equivalent of having this in the app.js?
In your app.js server file, include the model.js file like this:
var Model = require('./models/model'); //whatever you want to call it
You can then instantiate it in your server file like this:
//Initiate the Business API endpoints
var model = new Model(mq, siteConf);
model.getUser(id, function() {
// handle result
});
----
Then in your file you place in models folder named model.js (or whatever you want) you can set it up like this:
var mongoose = require('mongoose');
//MongoDB schemas
var Schema = mongoose.Schema;
var User = new Schema({
username: String,
password: String,
first_name: String,
last_name: String,
email: String
});
var UserModel = mongoose.model('User', User);
// your other objects defined ...
module.exports = function(mq, siteConf) {
//MongoDB
mongoose.connect(siteConf.mongoDbUrl);
// ------------------------
// READ API
// ------------------------
// Returns a user by ID
function getUser(id, found) {
console.log("find user by id: " + id);
UserModel.findById(id, found);
}
// Returns one user matching the given criteria
// mainly used to match against email/login during login
function getUserByCriteria(criteria, found) {
console.log("find user by criteria: " + JSON.stringify(criteria));
UserModel.findOne(criteria, found);
}
// more functions for your app ...
return {
'getUser': getUser,
'getUserByCriteria': getUserByCriteria
};
};

Resources