Trouble manually setting the model property using mongoose - node.js

I have a simple model, which is:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var citySchema = new Schema({
name: { type: String, required: true },
state: { type: Schema.Types.ObjectId, ref: 'State' }
});
module.exports = mongoose.model('City', citySchema);
Only i access a route, asking to insert a city, giving as post params
POST: {
name: 'My City',
state: 'SE' // Stands for Some State
}
I know the type of the state property is not correct, but in my logic i do:
var newCity = new City(req.body);
if (typeof req.body.state !== 'undefined' && req.body.state.length == 2) {
State.findOne({uf: req.body.state.toUpperCase()}, function(err, foundState) {
if (err) { res.send({status: 500, message: 'Could not find the required state'}); return; }
newCity.state = foundState._id;
newCity.set('state', foundState._id);
return;
});
}
But, once i do a res.send(newCity), to check out the newCity variable properties, it prints:
{
"name": "Balneário Camború",
"_id": "570ff2944c6bd6df4e8e76e8"
}
And if i try to save it, i get the following error:
ValidationError: CastError: Cast to ObjectID failed for value \"SE\" at path \"state\""
So, i'm quite confused, because when the Model is created using the req.body properties, it does not list the state property, even if i set it later in the code, but when i try to save the City, it throw an error of mistype.
What could be causing this, and how should i procede?

Related

Autoincrement with Mongoose

I'm trying to implement an autoicremental user_key field. Looking on this site I came across two questions relevant for my problem but I don't clearly understand what I should do. This is the main one
I have two Mongoose models, this is my ProductsCounterModel.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var Counter = new Schema({
_id: {type: String, required: true},
sequence_value: {type: Number, default: 0}
});
module.exports = mongoose.model('products_counter', Counter);
and this is the Mongoose model where I try to implement the auto-increment field:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var products_counter = require('./ProductsCounterModel.js');
var HistoricalProduct = new Schema({
product_key: { type: Number },
class: { type: String },
brand: { type: String },
model: { type: String },
description: { type: String }
});
HistoricalProduct.pre("save", function (next) {
console.log("first console log:",products_counter);
var doc = this;
products_counter.findOneAndUpdate(
{ "_id": "product_key" },
{ "$inc": { "sequence_value": 1 } },
function(error, products_counter) {
if(error) return next(error);
console.log("second console log",products_counter);
doc.product_key = products_counter.sequence_value;
next();
});
});
module.exports = mongoose.model('HistoricalProduct', HistoricalProduct);
Following the steps provided in the above SO answer I created the collection products_counter and inserted one document.
The thing is that I'm getting this error when I try to insert a new product:
"TypeError: Cannot read property 'sequence_value' of null"
This are the outputs of the above console logs.
first console log output:
function model (doc, fields, skipId) {
if (!(this instanceof model))
return new model(doc, fields, skipId);
Model.call(this, doc, fields, skipId);
}
second console log:
Null
can you see what I'm doing wrong?
You can run following line in your middleware:
console.log(products_counter.collection.collectionName);
that line will print products_counters while you expect that your code will hit products_counter. According to the docs:
Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. Set this option if you need a different name for your collection.
So you should either rename collection products_counter to products_counters or explicitly configure collection name in your schema definition:
var Counter = new Schema({
_id: {type: String, required: true},
sequence_value: {type: Number, default: 0}
}, { collection: "products_counter" });

MongoDB : Missing the key _id in the item

I have been given some code to modify. It is a Node.js app using Mongoose to interact with a MongoDb instance. In Mongoose several schemas were already set up and I've added a few. Among those are these two schemas which break apart a previously existing schema (which was working fine with small data):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MapConvertedProjectSchema = new Schema(
{
project_id : {
type: String,
default: ""
},
dataset_id : {
type: String,
default: ""
},
properties:{
type: {},
default: {}
}
});
MapConvertedProjectSchema.pre('save', function(next) {
next();
});
mongoose.model('MapConvertedProject', MapConvertedProjectSchema);
and
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MapConvertedLayerSchema = new Schema(
{
parent_id:
{
type: mongoose.Schema.Types.ObjectId
},
class:
{
type: String,
default: 'MapLayer',
trim: true
},
properties:
{
type: {},
default: {}
}
});
//Hook a pre save method to clean date
MapConvertedLayerSchema.pre('save', function(next) {
next();
});
mongoose.model('MapConvertedLayer', MapConvertedLayerSchema);
I use the MapConvertedLayer schema like so:
var mongoose = require('mongoose');
var LayerConverted = mongoose.model('MapConvertedLayer');
var newLayer = new LayerConverted();
//newLayer._id is automatically populated with a value
//... add other properties
newLayer.save(function(err)
{
if(err)
{
//...
}
});
This works without any issues that I can discern. However if I try similar code with MapConvertedProject I get an error:
var mongoose = require('mongoose');
var ProjectConverted = mongoose.model('MapConvertedProject');
var map_converted = new ProjectConverted();
//map_converted._id is undefined
//I tried adding the comment below to create an _id manually, but it didn't make a difference when I tried to save
//map_converted._id = mongoose.Types.ObjectId();
console.log("Project Converted ID: " + map_converted._id);
//... fill out the other properties on the schema
map_converted.save(function(err)
{
if(err)
{
//...
}
});
The save generates this error:
ValidationException: One or more parameter values were invalid: Missing the key _id in the item
Does anyone know what is causing this?
I figured this out. There was another place in the code that had a dynamoose model with the same name that was messing things up. I was able to remove all references to dynamoose since it doesn't appear to be used anymore and that cleared up this issue.

Get Parent Schema Property for Validation with Mongoose and MongoDB

Let's say that I have a nested Schema (invitation) in another schema (item) and when trying to save an invitation object, I want to check if the parent property 'enabled' from the item schema is set to true or false before allowing the person to save the invite object to the invitations array. Obviously, this.enabled doesn't work as it's trying to get it off of the invitationSchema, which doesn't exist. How would one get the 'enabled' property on the parent schema to validate?
Any thoughts? Thanks for any help!
var validateType = function(v) {
if (v === 'whateverCheck') {
return this.enabled || false; <== this doesn't work
} else {
return true;
}
};
var invitationSchema = new Schema({
name: { type: String },
type: {
type: String,
validate: [validateType, 'error message.']
}
});
var itemSchema = new Schema({
title: { type: String },
description: { type: String },
enabled: {type: Boolean}, <== trying to access this here
invitations: { type: [ invitationSchema ] },
});
var ItemModel = mongoose.model('Item', itemSchema, 'items');
var InvitationModel = mongoose.model('Invitation', invitationSchema);
The parent of an embedded document is accessible from an embedded doc model instance by calling instance.parent();. So you can do this from any Mongoose middleware like a validator or a hook.
In your case you can do :
var validateType = function(v) {
if (v === 'whateverCheck') {
return this.parent().enabled || false; // <== this does work
} else {
return true;
}
};

Mongoose schema inheritance and model populate

I have been trying this with the built in inheritance features of mongoose (rather than the extend plugin) but haven't been having much luck so far. This is a simplified example of code I am trying to use which exhibits the same problem. This is based on an expanded version of the mongoose documentation for schema inheritance using discriminators - http://mongoosejs.com/docs/api.html#model_Model.discriminator
var util = require('util');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/problem');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: String,
createdAt: Date
});
}
util.inherits(BaseSchema, Schema);
var BossStatusSchema = new Schema({
status: String
});
var BossStatus = mongoose.model('BossStatus', BossStatusSchema);
var PersonSchema = new BaseSchema();
var Person = mongoose.model('Person', PersonSchema);
var BossSchema = new BaseSchema({
department: String,
bossStatus: {
type: ObjectId,
ref: 'BossStatus'
}
});
var Boss = Person.discriminator('Boss', BossSchema);
Example code to add the documents:
var superBoss = new BossStatus({
status: 'super'
});
var normalBoss = new BossStatus({
status: 'normal'
});
var andy = new Person({
name: 'Andy'
});
var billy = new Boss({
name: 'Billy',
bossStatus: superBoss._id
});
var callback = function(err, result) {
console.dir(err);
console.dir(result);
};
superBoss.save(callback);
normalBoss.save(callback);
andy.save(callback);
billy.save(callback);
So when finding a record without populate:
Person
.findOne({
name: 'Billy'
})
.exec(callback);
The result is as expected, the bossStatus refers to an _id from the bossstatuses collection:
null
{ name: 'Billy',
bossStatus: 52a20ab0185a7f4530000001,
_id: 52a20ab0185a7f4530000004,
__v: 0,
__t: 'Boss' }
When adding the populate call:
Person
.findOne({
name: 'Billy'
})
.populate('bossStatus')
.exec(callback);
The resulting bossStatus property of the Person result is null:
null
{ name: 'Billy',
bossStatus: null,
_id: 52a20ab0185a7f4530000004,
__v: 0,
__t: 'Boss' }
EDIT:
Ok I've just put together what is probably a better example of what I'm trying to achieve, the schema structure lends itself more to a relational DB but hopefully makes the problem clearer.
var util = require('util');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/problem');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: {
type: String,
unique: true,
required: true
}
});
}
util.inherits(BaseSchema, Schema);
var DeviceSchema = new BaseSchema();
var LocalDeviceSchema = new BaseSchema({
driver: {
type: ObjectId,
ref: 'Driver'
}
});
var RemoteDeviceSchema = new BaseSchema({
networkAddress: {
type: ObjectId,
ref: 'NetworkAddress'
}
});
var DriverSchema = new Schema({
name: {
type: String,
unique: true,
required: true
}
});
var NetworkHostSchema = new Schema({
host: {
type: String,
unique: true,
required: true
}
});
var NetworkAddressSchema = new Schema({
networkHost: {
type: ObjectId,
ref: 'NetworkHost'
},
port: {
type: Number,
min: 1,
max: 65535
}
});
var Driver = mongoose.model('Driver', DriverSchema);
var NetworkHost = mongoose.model('NetworkHost', NetworkHostSchema);
var NetworkAddress = mongoose.model('NetworkAddress', NetworkAddressSchema);
var Device = mongoose.model('Device', DeviceSchema);
var LocalDevice = Device.discriminator('LocalDevice', LocalDeviceSchema);
var RemoteDevice = Device.discriminator('RemoteDevice', RemoteDeviceSchema);
var networkHost = new NetworkHost({
host: '192.168.2.1'
});
var networkAddress = new NetworkAddress({
networkHost: networkHost._id,
port: 3000
});
var remoteDevice = new RemoteDevice({
name: 'myRemoteDevice',
networkAddress: networkAddress._id
});
var driver = new Driver({
name: 'ftdi'
});
var localDevice = new LocalDevice({
name: 'myLocalDevice',
driver: driver._id
});
var callback = function(err, result) {
if(err) {
console.log(err);
}
console.dir(result);
};
/*
// Uncomment to save documents
networkHost.save(function() {
networkAddress.save(function() {
remoteDevice.save(callback);
});
});
driver.save(function() {
localDevice.save(callback);
});
*/
var deviceCallback = function(err, device) {
if(err) {
console.log(err);
}
switch(device.__t) {
case 'LocalDevice':
console.log('Would create a local device instance passing populated result');
break;
case 'RemoteDevice':
console.log('Would create a remote device instance passing populated result');
break;
}
};
Device
.findOne({name: 'myLocalDevice'})
.populate('driver')
.exec(deviceCallback);
The LocalDevice and RemoteDevice schemas could (and probably would) include other differences..
The switch would for example use a DeviceFactory or something to create the instances. My thinking was it should be possible to search the devices table for a device by 'name' and populate the collection references (if this is the correct terminology?) without having to specify the collection to search in - this was my understanding of the point of schema inheritance - or have I completely misunderstood?
Thanks for replies so far!
You are looking for a Boss, not a Person:
Boss
.findOne({
name: 'Billy'
})
.populate('bossStatus')
.exec(callback);
Looks like a bug. With debugging active, this is what's being shown for the population query:
Mongoose: people.findOne({ name: 'Billy' }) { fields: undefined }
Mongoose: people.find({ _id: { '$in': [ ObjectId("52a221ee639cc03d71000001") ] } }) { fields: undefined }
(the ObjectId shown is the one stored in bossStatus)
So Mongoose is querying the wrong collection (people instead of bossstatuses).
As #regretoverflow pointed out, if you're looking for a boss, use the Boss model and not the Person model.
If you do want to populate bossStatus through the Person model, you can explicitly state a model that needs to be searched for population:
.populate({
path : 'bossStatus',
model : 'BossStatus'
})
// or shorter but less clear:
// .populate('bossStatus', {}, 'BossStatus')
EDIT: (with your Device examples)
driver is part of LocalDeviceSchema, but you're querying the Device model, which has no notion of what driver is and populating driver within the context of a Device instance doesn't make sense to Mongoose.
Another possibility for populating each instance is to do it after you retrieved the document. You already have the deviceCallback function, and this will probably work:
var deviceCallback = function(err, device) {
if(err) {
console.log(err);
}
switch(device.__t) { // or `device.constructor.modelName`
case 'LocalDevice':
device.populate('driver', ...);
break;
case 'RemoteDevice':
device.populate('networkAddress', ...);
break;
}
};
The reason is that the document is already cast into the correct model there, something that apparently doesn't happen when you chain populate with the find.

Mongoose updates a field with null value throws exception

Here is the model definition:
pricing: { type: Number, default: null }
this is the exception I get when Mongoose tries to update a record with the source data being null:
Error message I got:
message: 'Cast to number failed for value "NaN" at path "pricing"',
name: 'CastError',
type: 'number',
value: NaN,
path: 'pricing'
I do need to update the existing value with null for this case since the application treat the field to be a null-able Number field.
How to fix it? Thanks in advance!
My guess is that it is trying to cast null to a Number. Try setting the default to 0;
Inserting null values for pricing seems to work okay for me; see the sample code I used below:
app.js
var mongoose = require("mongoose"),
Widget = require("./model.js").model;
// connect to mongo
mongoose.connect("mongodb://localhost/testdb");
// test insertion
var testValid = new Widget({ name: "Valid Test", price: 10.00 }),
testInvalid = new Widget({ name: "Invalid Test", price: null });
testValid.save();
testInvalid.save(function (err) {
if (err) {
console.log(err);
}
});
model.js
var mongoose = require("mongoose");
var schema = new mongoose.Schema({
name: String,
price: { type: Number, default: null }
});
exports.model = mongoose.model("Widget", schema);
exports.schema = schema;
Judging by your error message, it would appear that an invalid calculation is being made, the result of which is attempting to insert into Mongo. "value" in the error message is the value that was trying to be inserted. You can verify this by attempting to save the following document:
var testInvalid = new Widget({ name: "Invalid Test", price: "abc"});
Which will result in:
{ message: 'Cast to number failed for value "abc" at path "price"',
name: 'CastError',
type: 'number',
value: 'abc',
path: 'price' }
Are they any more details or code samples you could share?
EDIT:
I would try isolating the update from the rest of your code and seeing if you can duplicate the error, like this:
var mongoose = require("mongoose"),
Widget = require("./model.js").model;
// connect to mongo
mongoose.connect("mongodb://localhost/testdb");
// insert record
var testInvalid = new Widget({ name: "Invalid Test", price: 10.00 });
// update record
testInvalid.save(function (err) {
if (err) {
console.log(err);
} else {
Widget.findOne({ name: "Invalid Test" }, function (err, record) {
// set price to null & save
record.price = null;
record.save(function (err) {
if (err) {
console.log(err);
}
});
});
}
});
This isolated update appeared to work for me. Sorry I can't be of more help :/
NaN != null is the best way to describe the problem.
You should check the value of the price you are trying to insert for 'NaN'.
If this tests to true then insert a null in your document, otherwise insert the correctly parsed pricing (as a Number).

Resources