Mongoose: Cast to ObjectId failed - node.js

I am trying to create a hierarchy of categories in MongoDB for use with Node.js via Mongoose. I am using the Array of Ancestors approach (http://docs.mongodb.org/manual/tutorial/model-tree-structures-with-ancestors-array/) and have already saved the hierarchy in the database. Directly from Mongo an element looks like this:
{
"_id" : "Football",
"ancestors" : [
"Categories",
"Sports and fitness"
],
"parent" : "Sports and fitness"
}
I have created a model and controller for the categories, and are as of now having problems querying the database.
This is the code in model/Category.js:
var mongoose = require('mongoose');
var Category = mongoose.Schema({
_id: String
});
var categorySchema = mongoose.Schema({
ancestors: [Category],
parent: [Category]
});
// Initiate database connection
var db = mongoose.createConnection('mongodb://localhost/Categories');
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback () {
console.log("openDB categories");
});
module.exports.category = db.model('Category', categorySchema);
This is the controller:
var categoryModel = require('../models/Category');
var Category = categoryModel.category;
exports.getAncestors = function(req, res) {
if (req.params.id == undefined){res.send("no id specified!"); return;}
Category.findOne({_id: 'Football'}, 'ancestors', function(err, ancestors){
if(err) console.log(err);
res.send(ancestors);
});
}
When running this code I get the following error message:
{ message: 'Cast to ObjectId failed for value "Football" at path "_id"',
name: 'CastError',
type: 'ObjectId',
value: 'Football',
path: '_id' }
I believe the problem may be in the mongoose schema, but all help is greatly appreciated.
Many thanks!

Mongoose tries to set an ObjectId by default. You can suppress this with the following:
var categorySchema = mongoose.Schema({
_id: String,
ancestors: [{type: String }],
parent: {type: String}
},{ _id: false });
var Category = mongoose.model( "Category", categorySchema );
And noting that there is only one schema for you layout.

Related

Mongodb with Node Js

How can i execute this mongodb query through node js (mongoose).
i have two tables with the follwoing schemas,
i want to fetch username and password from users table and fetch full name from the info table.
var infoSchema = mongoose.Schema({
khatam_id: String,
user_id: String,
fullname: String,
});
var usersSchema = mongoose.Schema({
user_id: String,
username: String,
password: String,
});
i don't know if you're a hot shot but if you are you can use this.
userSchema.virtual('infos',
{
ref: 'Info',
localField: 'user_id',
foreignField: 'user_id',
})
Assuming u name you're infoSchema as Info model ,mongodb converts it to infos in case you didn't know thats why the virtual field will be called as infos ref obviously a reference to Info model
localField is the field referring the userSchema unique id and foreignField is the field u are referring in the infoSchema which matches the unique value that localfield u mentioned .
Finally, along with all the fields in your userSchema add this
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
so when u query for user
Give it a shot it really comes in handy.
Note: it doesn't actually create a field in the database (virtual duh.) it just populates your response object for front end rendering which is actually better.
Connect to MongoDB: Make sure MongoDB service is running before the program execution.
var mongoose = require("mongoose");
mongoose.connect("mongodb://localhost:27017/your-database");
mongoose.Promise = global.Promise;
var connection = mongoose.connection;
connection.once('open', function() {
console.log('connected to database);
});
connection.on('error', function() {
console.error('Mongoose connection error");
});
process.on('SIGINT', function() {
mongoose.connection.close(function() {
console.log('Mongoose connection disconnected due to app SIGINT.');
});
});
create user schema :
const Schema = mongoose.Schema;
const usersSchema = new Schema({
user_id: String,
username: String,
fullname: String
});
const Users = mongoose.model('Users', usersSchema );
Run Query like this:
Users.findOne({query})
.then(function(user){
// do something
})
.catch(function(err){
// handle error
})
Assuming you know how to set setup mongoose and connect your database and only need the correct query let me share something i noticed with the models you provided. I don't think there is a need for 2 collections as you can store the same thing under one collection because it saves the time for lookup for the user data from info schema. So user schema can be
var usersSchema = mongoose.Schema({
user_id: String,
username: String,
password: String,
khatam_id: String,
fullname: String
});
And using the following query you can get the user details
Users.findOne({})
.then(function(user){
// do something
})
.catch(function(err){
// handle error
})
This is much more efficient and faster compared to using aggregate queries or mongoose populate functions.
If the above method is not suitable for you you can try the mongoose populate function.
UserSchema = new mongoose.Schema({
user_id: String,
password: String,
},
// schema options: Don't forget this option
// if you declare foreign keys for this schema afterwards.
{
toObject: {virtuals:true},
// use if your results might be retrieved as JSON
// see http://stackoverflow.com/q/13133911/488666
//toJSON: {virtuals:true}
});
UserInfoSchema = new mongoose.Schema({
user_id: String,
khatam_id: String,
username: String,
fullname: String,
});
// Foreign keys definitions
UserSchema.virtual('userDetails', {
ref: 'UserInfoSchema',
localField: 'user_id',
foreignField: 'user_id',
justOne: true // for many-to-1 relationships
});
// Models creation
var UserSchema = mongoose.model('UserSchema', UserSchema);
var UserInfoSchema = mongoose.model('UserInfoSchema', UserInfoSchema);
// Querying
UserSchema.find({...})
// if you use select() be sure to include the foreign key field !
.select({.... user_id ....})
// use the 'virtual population' name
.populate('userDetails')
.exec(function(err, books) {...})
install mongoose in your machine
npm install mongoose
import mongoose lib
const mongoose = require('mongoose');
const Schema = mongoose.Schema, ObjectId = Schema.ObjectId;
connect to mongodb and create schema for your table
mongoose.connect('mongodb://localhost/myappdatabase');
const usersSchema = new Schema({
_id: ObjectId,
user_id: String,
username: String,
fullname: String
});
const Users = mongoose.model('Users', usersSchema );
Find user from "Users" table using mongo query
Users.find({<here you can write your mongo query>}, function (err, docs) {
// docs.forEach
});
you have to use aggregate
db.users.aggregate([
{
$lookup:
{
from: "info",
localField: "user_id",
foreignField: "_id",
as: "userInfo"
}
},
{ "$unwind": "$userInfo" },
])
You can use this query to get those types of records what you want:
db.users.aggregate([
{ $lookup: {
from: "info",
localField: "user_id",
foreignField: "user_id",
as: "userInfo"}},
{ "$unwind": "$userInfo" }])

How to associate the ObjectId of the parent schema to the child schema in the same transaction?

I have two types of entities, users and families, with a many-to-one relationship between families and users, so one family can have many users but one user will only have one family. I attempted to create a Mongo schema that tries to achieve this relationship, but not sure if this is the right way to do it.
I have a button on my HTML page that when clicked, will generate a family code and also create a new family attribute for the family entity. However, I'm unable to connect that newly generated family.ObjectId to the user's familyid attribute in the user entity.
Any idea what I'm doing wrong?
My Models:
Family Model:
var mongoose = require("mongoose");
var shortid = require('shortid');
//Set Schema
var familySchema = mongoose.Schema({
individuals: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}],
familyCode: {
type: String,
'default': shortid.generate
}
});
//setup and export the model
module.exports = mongoose.model("Family", familySchema);
Users Model:
var mongoose = require("mongoose");
var passportLocalMongoose = require("passport-local-mongoose");
var UserSchema = new mongoose.Schema({
username: String,
password: String,
image: String,
firstName: String,
lastName: String,
accountid: String,
isAdmin: {type: Boolean, default: false},
userlabel: String,
familyid:
{ type : mongoose.Schema.Types.ObjectId,
ref: "Family"}
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
My Route:
router.post("/familySetup", function(req, res){
if(!req.body.familyid){
var familyCode = shortid.generate();
console.log(familyCode);
// var individuals = {id:req.user._id}
var newFamily = {familyCode:familyCode};
Family.create(newFamily, function(err, newFamily){
if(err){
req.flash("error", "something seems to have gone amiss. Please try again.")
console.log(err);
res.redirect("home")
}else{
var updatedUser = {familyid:newFamily._id}
console.log(updatedUser);
User.findByIdAndUpdate(req.params.user_id, updatedUser, function(req, res){
if(err){
console.log(err);
}else{
//redirect to index
res.redirect("/familySetup");
}
});
}
});
}else{
alert("You already have a family account");
res.redirect("back");
}
});
Based on the error I get, I am able to create a familyCode and am able to create the variable, updatedUser. But it does not update the user with the new attribute. This is the error when I run the code:
ByJyRqb_Z
{ familyid: 59941dd6589f9a1c14a36550 }
events.js:141
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'redirect' of null
at /home/ubuntu/workspace/mbswalay/routes/family.js:44:28
at Query.<anonymous> (/home/ubuntu/workspace/mbswalay/node_modules/mongoose/lib/model.js:3755:16)
at /home/ubuntu/workspace/mbswalay/node_modules/mongoose/node_modules/kareem/index.js:277:21
at /home/ubuntu/workspace/mbswalay/node_modules/mongoose/node_modules/kareem/index.js:131:16
at nextTickCallbackWith0Args (node.js:436:9)
at process._tickCallback (node.js:365:13)
It doesn't seem to be an error of mongodb, it's related to the route configuration, which seems make the response object to be null
User.updateOne(
{ _id: req.params.user_id },
{ $set: { familyid: newFamily._id } }
)
You have to set the value of an attribute using $set operator.

mongoose#populate returns null in nested object inside array

I have a mongoDB database which is generated using a script that uses only the node.js mongoDB driver without mongoose. Later on, in the application, I want to use mongoose to load a document and have a reference be populated automatically; however, this only ever returns null.
Imagine a task which contains sub-items which each have a title and an assigned person. The assigned person, in this case, is the reference I want to have populated, so the reference lives in an object inside an array in the task schema.
The following code (requiring npm install mongodb mongoose) reproduces the problem (watch out, it destroys a local database named test if you have one already):
const mongodb = require('mongodb');
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
(async () => {
// Step 1: Insert data. This is done using the mongodb driver without mongoose.
const db = await mongodb.MongoClient.connect('mongodb://localhost/test');
await db.dropDatabase();
await db.collection('persons').insertOne({ name: 'Joe' });
const joe = await db.collection('persons').findOne({ name: 'Joe' });
await db.collection('tasks').insertOne({ items: [{ title: 'Test', person: joe._id }] });
await db.close();
// ================
// Step 2: Create the schemas and models.
const PersonSchema = new Schema({
name: String,
});
const Person = mongoose.model('Person', PersonSchema);
const TaskSchema = new Schema({
items: [{
title: String,
person: { type: Schema.Types.ObjectId, ref: 'Person' },
}],
});
const Task = mongoose.model('Task', TaskSchema);
// ================
// Step 3: Try to query the task and have it populated.
mongoose.connect('mongodb://localhost/test');
mongoose.Promise = Promise;
const myTask = await Task.findOne({}).populate('items.person');
// :-( Unfortunately this prints only
// { _id: "594283a5957e327d4896d135", items: [ { title: 'Test', person: null } ] }
console.log(JSON.stringify(myTask, null, 4));
mongoose.connection.close();
})();
The expected output would be
{ _id: "594283a5957e327d4896d135", items: [ { title: 'Test', person: { _id: "594283a5957e327d4896d134", name: "Joe" } } ] }
I have verified that the two _ids actually match using mongo shell:
> db.persons.find({})
{ "_id" : ObjectId("594283a5957e327d4896d134"), "name" : "Joe" }
> db.tasks.find({})
{ "_id" : ObjectId("594283a5957e327d4896d135"), "items" : [ { "title" : "Test", "person" : ObjectId("594283a5957e327d4896d134") } ] }
What am I doing wrong when attempting to populate person? I am using mongoose 4.10.6 and mongodb 2.2.28.
The answer to this problem lies in the fact that the collection name mongoose automatically infers from the model Person is people and not persons.
The problem can be solved either by writing to the people collection in the first part or by forcing mongoose to use the collection name persons:
const Person = mongoose.model('Person', PersonSchema, 'persons');
mongoose plans to remove pluralization in the collection name anyway, see #1350 on Github.

Mongoose returning empty array of ObectID, which isn't

I am using the following Mongoose Schema :
var userSchema = new mongoose.Schema({
...
sentFriendsRequests: [{
type : ObjectId,
}]
)};
I am adding some ObjectIds to the sentFriendsRequests
User.update({ _id: userId },
{ $push: { sentFriendsRequests: targetId }},
{safe: true, upsert: true}, function(err, result) {
if (err || !result) {
done(err);
}
done(null);
});
This seems to be working properly, because as I am using Mongolab to host my Database, when displaying documents on screen I can see that the ObjectIds are added to the array with success :
"receivedFriendsRequests": [
"5720c659571a718705d58fc3"
]
The weird thing is that when querying this array, Mongoose always return an empty one...
User.find({ _id: userId}, function(err, res) {
console.log(res[0].sentFriendsRequests);
});
// prints []
Have confusion of mongodb with mongoose.
Mongoose need define Schema but mongodb is nope.
To define new ObjectId in mongodb:
var ObjectId = require('mongodb').ObjectID
var objectId = new ObjectID();
in Mongoose:
var mongoose = require('mongoose');
var objectId = new mongoose.Types.ObjectId;
I finally found that using var ObjectId = require('mongodb').ObjectID; makes Mongoose to return empty array, whereas using mongoose.Schema.Types.ObjectId works properly. I don't know how to explain this.

How to set ObjectId as a data type in mongoose

Using node.js, mongodb on mongoHQ and mongoose. I'm setting a schema for Categories. I would like to use the document ObjectId as my categoryId.
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
categoryId : ObjectId,
title : String,
sortIndex : String
});
I then run
var Category = mongoose.model('Schema_Category');
var category = new Category();
category.title = "Bicycles";
category.sortIndex = "3";
category.save(function(err) {
if (err) { throw err; }
console.log('saved');
mongoose.disconnect();
});
Notice that I don't provide a value for categoryId. I assumed mongoose will use the schema to generate it but the document has the usual "_id" and not "categoryId". What am I doing wrong?
Unlike traditional RBDMs, mongoDB doesn't allow you to define any random field as the primary key, the _id field MUST exist for all standard documents.
For this reason, it doesn't make sense to create a separate uuid field.
In mongoose, the ObjectId type is used not to create a new uuid, rather it is mostly used to reference other documents.
Here is an example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Product = new Schema({
categoryId : ObjectId, // a product references a category _id with type ObjectId
title : String,
price : Number
});
As you can see, it wouldn't make much sense to populate categoryId with a ObjectId.
However, if you do want a nicely named uuid field, mongoose provides virtual properties that allow you to proxy (reference) a field.
Check it out:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
title : String,
sortIndex : String
});
Schema_Category.virtual('categoryId').get(function() {
return this._id;
});
So now, whenever you call category.categoryId, mongoose just returns the _id instead.
You can also create a "set" method so that you can set virtual properties, check out this link
for more info
I was looking for a different answer for the question title, so maybe other people will be too.
To set type as an ObjectId (so you may reference author as the author of book, for example), you may do like:
const Book = mongoose.model('Book', {
author: {
type: mongoose.Schema.Types.ObjectId, // here you set the author ID
// from the Author colection,
// so you can reference it
required: true
},
title: {
type: String,
required: true
}
});
My solution on using ObjectId
// usermodel.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
UserSchema.set('autoIndex', true)
module.exports = mongoose.model('User', UserSchema)
Using mongoose's populate method
// controller.js
const mongoose = require('mongoose')
const User = require('./usermodel.js')
let query = User.findOne({ name: "Person" })
query.exec((err, user) => {
if (err) {
console.log(err)
}
user.events = events
// user.events is now an array of events
})
The solution provided by #dex worked for me. But I want to add something else that also worked for me: Use
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
if what you want to create is an Array reference. But if what you want is an Object reference, which is what I think you might be looking for anyway, remove the brackets from the value prop, like this:
let UserSchema = new Schema({
username: {
type: String
},
events: {
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}
})
Look at the 2 snippets well. In the second case, the value prop of key events does not have brackets over the object def.
You can directly define the ObjectId
var Schema = new mongoose.Schema({
categoryId : mongoose.Schema.Types.ObjectId,
title : String,
sortIndex : String
})
Note: You need to import the mongoose module
Another possible way is to transform your _id to something you like.
Here's an example with a Page-Document that I implemented for a project:
interface PageAttrs {
label: string
// ...
}
const pageSchema = new mongoose.Schema<PageDoc>(
{
label: {
type: String,
required: true
}
// ...
},
{
toJSON: {
transform(doc, ret) {
// modify ret directly
ret.id = ret._id
delete ret._id
}
}
}
)
pageSchema.statics.build = (attrs: PageAttrs) => {
return new Page({
label: attrs.label,
// ...
})
}
const Page = mongoose.model<PageDoc, PageModel>('Page', pageSchema)
Now you can directly access the property 'id', e.g. in a unit test like so:
it('implements optimistic concurrency', async () => {
const page = Page.build({
label: 'Root Page'
// ...
})
await page.save()
const firstInstance = await Page.findById(page.id)
const secondInstance = await Page.findById(page.id)
firstInstance!.set({ label: 'Main Page' })
secondInstance!.set({ label: 'Home Page' })
await firstInstance!.save()
try {
await secondInstance!.save()
} catch (err) {
console.error('Error:', err)
return
}
throw new Error('Should not reach this point')
})

Resources