Unable to Append Array Within MongoDB (mongoose) Document - node.js

I'm trying to append an empty array in a mongo database with a String that is created in the front-end (website ui), the relevant code snipets are as following:
mongoose Schema
email: String,
displayName: String,
googleId: String,
toIgnore: [{toIgnoreURL: String}]
})
document creation with passport & passport-google-oauth20
User.findOne({email: email.emails[0].value}).then((currentUser)=>{
if(currentUser){
// user does already exist
console.log('welcome back!', currentUser)
done(null, currentUser)
}
else{ // user doesn't exist yet
new User({
email: email.emails[0].value,
displayName: email.displayName,
googleId: email.id,
toIgnore: []
}).save().then((newUser)=>{
console.log('new user created: ' + newUser)
done(null, newUser)
});
}
})
And lastly the attempt to append the toIgnore array property of the 'User' collection (of the currently logged in user)
User.update(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}})
In mongodb I see that the following document is successfully created
_id
:ObjectId(
IdOfDocumentInMongoDB)
toIgnore
:
Array
email
:
"myactualtestemail"
googleId
:
"longgoogleidonlynumbers"
__v
:
0
(Also as seen in attached image)
document in mongodb ui
I don't seem to figure out how to actually populate the 'toIgnore' array.
For instance, when console logging the following
var ignoreList = User.findOne({email:emailThisSession}).toIgnore;
console.log(ignoreList)
The output is undefined
Note that console logging the url variable does indeed print the value I want to append to the array!
I tried any format combination I could think of in the Schema builder and document creation, but I can't find figure the right way to get it done!
Any help would be appreciated!
Update, using promises didn't work either
User.findOne({email:emailThisSession}).then((currentUser)=>{ //adding .exec() after findOne({query}) does not help as in User.findOne({email:emailThisSession}).exec().then(...)
console.log(currentUser.toIgnore, url) //output is empty array and proper value for url variable, empty array meaning []
currentUser.toIgnore.push(url)
});
While adjusting the Schema as follows:
const userSchema = new Schema({
email: String,
displayName: String,
googleId: String,
toIgnore: []
})
Solution
I simply needed to change the update command to
User.updateOne(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}}).then((user)=>{
console.log(user)
})
Thanks #yaya!

Unable to add array element within a document with mongoose
define your schema as:
const UserSchema = new mongoose.Schema({
...
toIgnore: [{toIgnoreURL: String}]
})
then you can create an object like this :
new User({
...,
toIgnore: [] // <-- optional, you can remove it
})
To check the value:
User.findOne({...}).then(user => {
console.log(user.toIgnore)
});
Your update statement should be:
User.update(
{email: emailThisSession},
{$push: {toIgnore: {toIgnoreURL: url}}}
).then(user => {
console.log(user)
})
So in your case, this is undefined:
User.findOne({email:emailThisSession}).toIgnore
Since findOne is async. for getting the result, you can whether pass it a callback or you can use promises (User.findOne({...}).then(user => console.log(user.toIgnore)))
Update:
While adjusting the Schema as follows: new Schema({..., toIgnore: []})
Here is the problem with your update. you should change it back to : toIgnore: [{toIgnoreURL: String}]

Related

How do I prevent Schema fields from being inserted into subdocument?

I'm making a dating app in node js and vue, and everything works however I wish to exclude password from being inserted into subdocument upon creation of a conversation. Right now I know that i can say .select('-password') when using User.findOne() but it doesn't work, when adding the user schema as a subdoc to my Conversations schema, which has user_one and user_two, each referring to a User schema. I need the password field, so I can't ommit it when creating a schema. Right Now my code looks like this:
User.findOne({ _id: fromUserId }, (errUserOne, userOne) => {
User.findOne({ _id: toUserId }, (errUserTwo, userTwo) => {
conversation = new Conversation({
_id: mongoose.Types.ObjectId(),
user_one: userOne,
user_two: userTwo
});
conversation.save();
const message = new Message({
_id: mongoose.Types.ObjectId(),
conversation_id: conversation._id,
message: req.body.message,
user_id: fromUserId
});
message.save();
res.sendStatus(201);
});
});
However this code saves the password to the Conversation collection, which I don't want.
User.find({ _id: :{ $in : [fromUserId,toUserId] }, { password:0 } , (err, userArray) => {
//your code goes here
});
Two things, You are querying two time for getting users. You can merge it into single query and for excluding the password field you can pass {password:0}. Which will exclude it in the documents.
also while you define Conversation schema don't make user_one and user_two type of user. Instead define only whatever properties of user you want to save like:
var Conversation = new Schema({
_id : ObjectId,
user_one : {
_id: ObjectId,
//all other fields
},
user_two : {
_id: ObjectId,
//all other fields
},
});

Upserting with Mongoose

I have a schema that is defined like
var UserSchema = mongoose.Schema({
user: {
ipAddress: String,
pollIDs: [{
id: String
}]
}
});
var User = module.exports = mongoose.model('User', UserSchema);
What I want to create is a route that checks the requests ip address, see if it exists in the database, if it doesn't create a new document with the ipAddress property set accordingly and the current req.body.poll_id to be an element in the pollIDs array.
However, if there is a document with that ip address I want the req.body.poll_id to be pushed into the pollIDs array.
I would demonstrate my first attempt, but I know that I've messed up the parameters on the findOneAndUpdate call.
Should be as simple as:
User.findOneAndUpdate(
{'user.ipAddress': req.body.ipAddress},
{$push: {'user.pollIDs': {id: req.body.poll_id}}},
{upsert: true, new: true},
(err, doc) => {...});
The upsert will take the query object and apply the update operation to it in the case where it needs to insert a new document.

Cast to objectid failed for value when calling findOne and populate

I am creating a Mongodb database using Mongoose with table structure similar to the one in the Group.js file listed below. When my code hits the findOne call, I am receiving the cast error listed below:
Cast to ObjectId failed for value \"{ FirstName: 'John',\n MiddleName: null,\n LastName: 'Doe'\n Address: '1234 Anywhere St.' }\" at path \"_id\"
Group.js (model file)
let UserSchema = mongoose.Schema({
FirstName: String,
MiddleName: String,
LastName: String,
Address: String
});
let User = mongoose.model(‘User', UserSchema);
let GroupSchema = mongoose.Schema({
name: type: String,
groupType: String,
users: [UserSchema]
});
module.exports = mongoose.model('Group', GroupSchema);
GroupRepository.js (method call that gets the group with their associated user lists)
findGroupPopulateUsers(paramList, cb){
this.group.findOne(paramList).populate('users').exec(function (err, entity) {
if (!err)
cb(err, entity);
else
cb(err);
});
}
Declaration for the group model before injecting into the GroupRepository class
const user = require('../models/Group');
Here is the paramList I am passing in to the findGroupPopulateUsers method call
{"groupType": "free"}
I am confused about what is triggering this error? I tried to follow the docs. on the mongoose website for handling subdocument population. Please let me know what I am missing in the rules about how subdocuments should be populated?
Here is a weird thing I am noticing, so the subdocument seems to return and I don't get the error if I don't specify the ".populate(....)" method call. However, if I try to access the "_id" field of the user subdocument, it keeps coming back undefined.
I think you should add .lean() for findOne function.
this.group.findOne(paramList).populate('users').lean().exec(function (err, entity) {
if (!err)
cb(err, entity);
else
cb(err);
});

Expressjs rest api how to deal with chaining functionality

I am building a restful API using express, mongoose and mongodb. It works all fine but I have a question about how to deal with requests that contain more functionality than just one find, delete or update in the database. My user model looks as follows:
var UserSchema = new Schema({
emailaddress: {type: String, unique: true},
firstname: String,
lastname: String,
password: String,
friends: [{type: Schema.Types.ObjectId, unique: true}]
});
As you can see is the friends array just an array of ObjectIds. These ObjectIds refer to specific users in the database. If I want to retrieve an array of a user's friends I now have to look up the user that makes the request, then find all the users that have the same id as in the friends array.
Now it looks like this:
methods.get_friends = function(req, res) {
//find user.
User.findOne({_id: req.params.id}, function(err, user, next) {
if(err) next(err);
if(user) {
console.log(user);
//find friends
User.find({_id: {$in: user.friends}}, {password: 0}).exec(function (err,
friends, next) {
if(err) next(err);
if(friends) {
res.send(friends);
};
});
}
Would it be possible to seperate the lookup of the user in a certain method and chain the methods? I saw something about middleware chaining i.e. app.get('/friends', getUser, getFriend)but would that mean that I have to alter the req object in my middleware (getUser) method and then pass it on? How would you solve this issue? Would you perhaps change the mongoose model and save all friend data (means that it could become outdated) or would you create a method getUser that returns a promise on which you would collect the friend data?
I will be grateful for all the help I can get!
Thank you in advance.
Mongoose has a feature called population which exists to help in these kinds of situations. Basically, Mongoose will perform the extra query/queries that are required to load the friends documents from the database:
User.findOne({_id: req.params.id})
.populate('friends')
.exec(function(err, user) {
...
});
This will load any related friends into user.friends (as an array).
If you want to add additional constraints (in your example, password : 0), you can do that too:
User.findOne({_id: req.params.id})
.populate({
path : 'friends'
match : { password : 0 },
})
.exec(function(err, user) {
...
});
See also this documentation.

Mongoose, find, return specific properties

I have this get call:
exports.getBIMFromProject = function(req, res){
mongoose.model('bim').find({projectId: req.params['prj_id']}, function(err, bim){
if(err){
console.error(err);
res.send(500)
}
res.send(200, bim);
});
};
Where do I specify which properties I want to return? Can't find it in the docs. The above returns the entire object. I only want a few properties returned.
This is my schema:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var bimSchema = new Schema({
projectId: Number,
user: String,
items:[
{
bimObjectId: Number,
typeId: String,
position:{
floor: String,
room:{
name: String,
number: String
}
}
}
]
});
mongoose.model('bim', bimSchema);
I don't want the items array included in my rest call.
You use projection. The first example in the mongoose query docs has a projection operation tucked in.
NB: not real code b/c I highlighted the important bits with triple stars
// find each person with a last name matching 'Ghost', ***selecting the `name` and `occupation` fields***
Person.findOne({ 'name.last': 'Ghost' }, ***'name occupation'***, function (err, person) {
if (err) return handleError(err);
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation) // Space Ghost is a talk show host.
})
The Person schema isn't specified but I think the example is clear enough.
Mongoose provides multiple ways to project documents with find, findOne, and findById.
1. Projection as String:
// INCLUDE SPECIFIC FIELDS
// find user and return only name and phone fields
User.findOne({ email: email }, 'name phone');
// EXCLUDE SPECIFIC FIELD
// find user and return all fields except password
User.findOne({ email: email }, '-password');
2. Projection by passing projection property:
// find user and return just _id field
User.findOne({ email: email }, {
projection: { _id: 1 }
});
3. Using .select method:
// find user and return just _id and name field
User.findOne({ email: email }).select('name');
// find user and return all fields except _id
User.findOne({ email: email }).select({ _id: 0 });
You can do the same with find and findById methods too.
MyModel.find({name: "john"}, 'name age address', function(err, docs) {
console.log(docs);
});
This will return fields - name, age and address only.
With the help of .select() this is possible.
If number of fields required are less from total fields then,
.select('projectId user') can be used
Else number of fields to be ignored are less from total fields then,
.select('-items') can be used.
So for getting a field, simply, space separated string of fields can be passed and for ignoring the field, space separated string with "-" before the field can be used.
For more documentation.
Find Specific properties also avoid some properties
await User.find({ email: email }, { name: 1, phone: 1, status: 1, password: 0 });
.Select() method is used to select which fields are to be returned in the query result
let result = await MyModel.find({ user : "user" }).select('name lastname status')

Resources