I am writing a small application that uses javascript, node, mongoDB, and mongoose. I have two collections; users and groups where every group contains an array of users
User:{_id:{type: String, required: true} FirstName: {type: String, required: true}, ..}
Group{_id:{type: String, required: true}, users:[{user: userSchema}] }
I am writing an api unit test using Mocha and Superagent. When I insert a sample document for the group that includes a nested objects for the users, I got a validation error?
Could you please let me know what is going wrong with this example?
var userSchema =
{
_id: {
type: String,
required: true,
},
profile: {
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
}
};
var GroupSchema =
{
_id: {
type: String,
required: true
},
users:[{
user: User.userSchema
}]
};
it('can query group by id', function(done) {
var users = [
{ _id: 'az', profile: {firstName: 'a', lastName: 'z'}},
{ _id: 'bz', profile: {firstName: 'b', lastName: 'z'}},
];
User.create(users, function(error, users) {
assert.ifError(error);
Group.create({ _id: 'ab', users: [{ _id: 'az', profile: {firstName: 'a', lastName: 'z'}}, { _id: 'bz', profile: {firstName: 'b', lastName: 'z'}}] }, function(error, doc) {
assert.ifError(error);
var url = URL_ROOT + '/api/groups/id/ab';
superagent.get(url, function(error, res) {
assert.ifError(error);
var result;
assert.doesNotThrow(function() {
result = JSON.parse(res.text);
});
assert.ok(result.group);
assert.equal(result.group._id, 'ab');
done();
});
});
});
});
Error Message:
Uncaught ValidationError: ChatGroup validation failed: users.1._id: Cast to ObjectID failed for value "bz" at path "_id", users.0._id: Cast to ObjectID failed for value "az" at path "_id", users.0.user.profile.lastName: Path `user.profile.lastName` is required., users.0.user.profile.firstName: Path `user.profile.firstName` is required., users.0.user._id: Path `user._id` is required., users.1.user.profile.lastName: Path `user.profile.lastName` is required., users.1.user.profile.firstName: Path `user.profile.firstName` is
I think your GroupSchema definition is incorrect:
var GroupSchema =
{
_id: {
type: String,
required: true
},
users:[{
user: User.userSchema
}]
};
The way you're using it in the test users array should have type of User.userSchema array:
var GroupSchema =
{
_id: {
type: String,
required: true
},
users:[{
type: User.userSchema // type, not 'user'
}]
// OR just: users: [User.userSchema]
};
Otherwise, if you still need to use your original schema, then in your test you should use it this way:
var users = [
{ user: { _id: 'az', profile: {firstName: 'a', lastName: 'z'}} },
{ user: { _id: 'bz', profile: {firstName: 'b', lastName: 'z'}} },
];
Related
I'm trying to populate my user requests.profileId but it returns only nulls.
I have the following schemas:
First Schema:
const profileSchema = new mongoose.Schema({
_id: { type: Number }, //<- _id is defined as a number which represents mobile number (easier for me to handle)
first: { type: String },
second: { type: String },
});
module.exports = mongoose.model('Profile', profileSchema, 'profiles');
Second Schema:
const userSchema = new mongoose.Schema({
firstName: { type: String },
lastName: { type: String },
requests: [
{
profileId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Profile',
},
requestTime: { type: Date, default: Date.now },
},
],
});
module.exports = mongoose.model('User', userSchema, 'users');
Here is my code:
const user = await User.findById(req.user).populate('requests.profileId');
console.log(user.requests);
Here is the output:
[
{
_id: 6201633869648e2b74c00a10,
profileId: null,
requestTime: 2022-02-07T18:21:44.722Z
},
{
_id: 6201633b69648e2b74c00a11,
profileId: null,
requestTime: 2022-02-07T18:21:47.238Z
},
{
_id: 620238f9d2b5dd3dee6c41a2,
profileId: null,
requestTime: 2022-02-08T09:33:45.176Z
},
{
_id: 620239253220343dfd7cfdd9,
profileId: null,
requestTime: 2022-02-08T09:34:29.780Z
}
]
Here is the output without populate:
[
{
_id: 6201633869648e2b74c00a10,
profileId: 393732353235303134343330, //<- typeof profileId is obeject
requestTime: 2022-02-07T18:21:44.722Z
},
{
_id: 6201633b69648e2b74c00a11,
profileId: 393732353435353333313131,
requestTime: 2022-02-07T18:21:47.238Z
},
{
_id: 620238f9d2b5dd3dee6c41a2,
profileId: 393732353435353333313131,
requestTime: 2022-02-08T09:33:45.176Z
},
{
_id: 620239253220343dfd7cfdd9,
profileId: 393732353435353333313131,
requestTime: 2022-02-08T09:34:29.780Z
}
]
Currently Profile.findById(mobileNumber) works fine.
Any ideas what went wrong?
Will greatly appreciate your assistance.
Thanks in advance :)
Try this might work let me know if it doesn't
const user = await User.findById(req.user).populate('Profile');
console.log(user.requests);
try this:
User.findById(req.user).populate({
path: 'requests',
populate: {
path: 'profileId',
model: 'Profile'
}
})
For future readers having the same issue!
I've found a solution for this issue.
I had to change the profileId type to Number in my userSchema:
const userSchema = new mongoose.Schema({
firstName: { type: String },
lastName: { type: String },
requests: [
{
profileId: {
type: Number // and NOT use type: mongoose.Schema.Types.ObjectId,
ref: 'Profile',
},
requestTime: { type: Date, default: Date.now },
},
],
});
module.exports = mongoose.model('User', userSchema, 'users');
Now it works!
I am trying to build a search function that searches on username or full name ... it works fine but if username like this "example.name" or "example_name" it did not return in result if I searched like this "examplename" or "example name" how to solve this problem to return a matched characters even that special characters
User Schema
const mongoose = require("mongoose");
const bcrypt = require("bcrypt-node");
const uniqueValidator = require("mongoose-unique-validator");
const schemaTypes = mongoose.Schema.Types;
const userSchema = new mongoose.Schema(
{
firstName: { type: String, required: true, trim: true },
lastName: { type: String, trim: true },
fullName: {type: String, trim: true},
username: { type: String, trim: true, unique: true, lowercase: true },
email: {
type: String,
unique: true,
trim: true,
lowercase: true,
required: true
},
password: { type: String, select: false },
gender: { type: String, default: "male" },
birthDate: Number,
location: { type: [Number], index: '2d' }, // [<longitude>, <latitude>]
bio: {type: String, default: ""},
profilePhoto: {type: String, default: "default-user-profile.jpg"},
private: { type: Boolean, default: false },
favouriteUsers: [{ type: schemaTypes.ObjectId, ref: "users" }],
blockedUsers: [{ type: schemaTypes.ObjectId, ref: "users" }],
tempPassword: String,
tempToken: String,
socialId: String,
favouriteRequests: [{ type: schemaTypes.ObjectId, ref: "users" }],
requests: [{ type: schemaTypes.ObjectId, ref: "users" }],
followers: [{ type: schemaTypes.ObjectId, ref: "users" }],
following: [{ type: schemaTypes.ObjectId, ref: "users" }],
playerId: {type: [String], default: []}
},
{ timestamps: true }
);
userSchema.plugin(uniqueValidator, { message: "This {VALUE} is used" });
userSchema.index({ "$**": "text" });
userSchema.pre("save", function(next) {
const user = this;
if (!user.isModified("password")) return next();
if (user.password) {
bcrypt.hash(user.password, null, null, function(err, hashedPassword) {
if (err) {
return;
}
user.password = hashedPassword;
next();
});
}
});
userSchema.pre("save", function(next) {
const user = this;
if (!user.isModified("firstName") && !user.isModified("lastName")) return
next();
if (user.firstName || user.lastName) {
user.fullName = user.firstName + " " + user.lastName;
next();
}
});
userSchema.methods.comparePassword = function(password) {
return bcrypt.compareSync(password, this.password);
};
module.exports = mongoose.model("users", userSchema);
Code
function search(keyword){
return new Promise((resolve,reject) => {
const User = require('../models/users');
let str = keyword.replace(/[`~!#$%^&*()|+\=?;:'",<>\{\}\[\]\\\/]/gi, "");
let key = new RegExp(str, "ig");
User.find({
$or: [
{ fullName: { $regex: key } },
{ username: { $regex: key } },
{ firstName: { $regex: key } },
{ lastName: { $regex: key } },
{ email: { $regex: key } }
]
}).exec(async (err, users) => {
if(err) return reject(err);
resolve(users);
})
})
}
Example 1
if keyword entered "ahmed ibrahim" or "ahmed.ibrahim" result is okay as expected
result for example 1
[
{
"_id": "5b40d19ae4fc082ca8f2ff3b",
"profilePhoto": "http://localhost/public/male-image.jpg",
"firstName": "Ahmed",
"lastName": "Ibrahim",
"username": "ahmed.ibrahim65356",
"email": "example#gmail.com",
"fullName": "Ahmed Ibrahim"
}
]
Exmaple 2 the problem
if the keyword is "ahmedibrahim"
I expected the previous result put returns nothing an empty array [] hot to solve this or any suggestion match characters even contain special characters
It won't work basically because you are searching for "ahmedibrahim" and it cannot relate with any of the fields in your document.
So for this specific scenario, what I would do is create a new field say "concatFullName" which should save as firstname+lastname, then use that field contactFullName in your find ie.
contactFullName : { $regex: key }
Try below sample for aggregate,
db.getCollection('test').aggregate({
$project: {
docs: "$$ROOT",
concatname: {
$concat: [
'$firstName',
'$lastName',
]
}
}
}, {
$match: {
concatname: {
$regex: "^ahmedIbrahim$",
$options: "i"
},
}
}, {
$group: {
_id: "$_id",
docs: {
$push: "$docs"
}
}
});
I have a User model and a Book model. I want some data from my books to be denormalized on each User document, but still have the option to populate if needed. If I set ref: 'Book' on the books.$._id it gets populated inside the _id path which is unintended. I would like the population to overwrite the denormalized data.
How do I accomplish this?
in users.model.js:
const { Schema } = require('mongoose');
const UserSchema = new Schema({
name: String,
books: {
type: [
{
_id: Schema.Types.ObjectId,
title: String,
length: Number,
},
],
default: [],
},
});
Desired outcome
in users.controller.js:
app.get('/', async (req, res, next) => {
const users = await User.find({})
/*
users: [{
_id: ObjectId(),
name: 'Andrew',
books: [{
_id: ObjectId(),
title: 'Game of Thrones',
length: 298,
}, { ... }],
}, { ... }]
*/
});
app.get('/:id', async (req, res, next) => {
const book_id = req.params.id;
const user = await User.findById(book_id).populate({
path: 'books',
model: 'Book',
});
/*
user: {
_id: ObjectId(),
name: 'Andrew',
books: [{
_id: ObjectId(),
name: 'Game of Thrones',
length: 298,
author: 'Simone Dunow',
releasedOn: Date(),
price: 30,
...
}, { ... }],
}
*/
});
Schemas I've tried so far:
books: {
type: [
{
_id: Schema.Types.ObjectId,
title: String,
length: Number,
},
],
default: [],
ref: 'Book',
},
returns array of { _id: null }
books: {
type: [
{
_id: {
type: Schema.Types.ObjectId,
ref: 'Book',
},
title: String,
length: Number,
},
],
default: [],
},
books are populated inside of _id: { _id: { Book } }
books: {
type: [
{
type: {
_id: Schema.Types.ObjectId,
title: String,
length: Number,
},
ref: 'Book',
},
],
default: [],
},
throws exception: invalid type
const UserSchema = new Schema({
name: String,
books: [{
id: { type : Schema.Types.ObjectId, ref : 'Book'} //Whatever string you have used while modeling your schema
title: String,
length: Number,
}],
});
While using the schema you can populate as follows :
populate({ path: 'books.id' })
Output :
{
_id : // someid
name : "somename"
books : [
{
id : {//document referring to Books collection},
title : "sometitle",
length : //somelength
}, ...
]
}
To anybody that might be still looking to achieve a full replacement, full disclosure: It might be a bit hacky for some evangelists or even have a performance toll on high traffic apps, but if you really want to do it, you can tap into the toJSON method of the schema like the following:
UserSchema.method('toJSON', function () {
let obj = this.toObject();
obj.books = obj.books.map(
(book) => (Schema.Types.ObjectId.isValid(book.id)) ? book : book.id
);
return obj;
});
What's going on here is basically we're replacing the whole property with the populated result when the book.id has been populated otherwise we just return the original object by checking the validity of the book's id (when populated will be a full bloomed object rather than an id).
I have an application which generates the following JSON object/array which I want to save under my user in the database.
[ [ { _id: '5990e6d4fce2e705f8d74923',
fname: 'test',
lname: 'testerson1',
username: 'email1#gmail.com',
__v: 0,
userid: '5990e6b9fce2e705f8d74922' },
{ _id: '5990e6e6fce2e705f8d74924',
fname: 'test',
lname: 'testerson2',
username: 'email2#gmail.com',
__v: 0,
userid: '5990e6b9fce2e705f8d74922' } ],
[ { _id: '5990e6f7fce2e705f8d74925',
fname: 'test',
lname: 'testerson3',
username: 'email3#gmail.com',
__v: 0,
userid: '5990e6b9fce2e705f8d74922' },
{ _id: '5990e707fce2e705f8d74926',
fname: 'test',
lname: 'testerson4',
username: 'email4#gmail.com',
__v: 0,
userid: '5990e6b9fce2e705f8d74922' } ] ]
What I am not sure of is how to define this in my model, what I have tried is:
var UserSchema = new mongoose.Schema({
fname: String,
lname: String,
username: String,
password: String,
testers: [
[
{
type: mongoose.Schema.Types.ObjectId,
ref: "Tester"
}
]
]
});
With this approach, I always get MongooseError: Cast to ObjectId failed for value "[ { _id: '5990e6d4fce2e705f8d74923' error message...which I am thinking is because the array I am trying to save does not have an ObjectId.
I have also tried removing the type and ref from the schema and leaving just the array, this works...my data JSON object/array is saved fine, but as I am still learning Mongo/Mongoose, I would like to know how to define this properly.
Thanks!
EDIT:
router.post("/matchtesters", middleware.isLoggedIn, function(req, res) {
User.findById(req.user._id, function(err, user){
if (err) {
console.log(err);
} else {
Tester.find({ "userid": req.user._id }, function(err, beans) {
if (err) {
console.log(err);
} else {
console.log("TEST2: ", req.body);
console.log("TEST3: " + JSON.stringify(req.body));
user.testers.push(req.body);
user.save();
}
});
}
});
});
I am using MongoDB + Node.js to create an app, but I am receiving an error for this code:
company.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var companySchema = Schema({
creator: {
type: Schema.Types.ObjectId,
ref: 'User'
},
name: String,
description: String,
categories: [String],
website: String,
address: String,
statuses: [{
date: { type: Date, default: Date.now },
status: String
}],
friends: [{
name: String,
description: String
}],
priority: Number,
isActive: Boolean,
contacts: [{
type: Schema.Types.ObjectId,
ref: 'ContactPerson'
}],
createdOn: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Company', companySchema);
api.js
api.route('/company')
.post(function(req, res) {
var company = new Company({
creator: req.decoded.id,
name: req.body.name,
description: req.body.description,
categories: req.body.categories,
website: req.body.website,
address: req.body.address,
friends.name: req.body.friendName,
statuses: { status: "New Company" },
priority: req.body.priority,
});
company.save(function(err, newCompany) {
if(err) {
res.send(err);
return
}
res.json({ message: "New company has been created!" });
});
})
Error
friends.name: req.body.friendName,
SyntaxError: Unexpected token .
I also tried with friend['name'] but the result was the same: SyntaxError: Unexpected token [
Please tell me where is my mistake
Try:
var company = new Company({
creator: req.decoded.id,
name: req.body.name,
description: req.body.description,
categories: req.body.categories,
website: req.body.website,
address: req.body.address,
friends: {
name: req.body.friendName
},
statuses: { status: "New Company" },
priority: req.body.priority,
});
or:
var company = new Company({
creator: req.decoded.id,
name: req.body.name,
description: req.body.description,
categories: req.body.categories,
website: req.body.website,
address: req.body.address,
'friends.name': req.body.friendName,
statuses: { status: "New Company" },
priority: req.body.priority,
});
depend on what you're after.
Use latest version of nvm and then run it
Write nvm use version 16.
And then write: npx nodemon index.