Mongoose/Mongodb: Exclude fields from populated query data - javascript

I use the following mongoose query in a MEAN-environment to find and output a particular author and his corresponding books.
Author
.findOne({personcode: code})
.select('-_id')
.select('-__v')
.populate('bookids') //referencing to book documents in another collection (->array of bookids)
.select('-_id') //this doens't affect the data coming from the bookids-documents
.select('-__v') //this doens't affect the data coming from the bookids-documents
.exec(function (err, data) {
//foo
});
I would also like to exclude the "_id" and "__v" fields from the populated data coming from the external documents. How can that be achieved?

The second parameter of populate is a field selection string, so you can do this as:
Author
.findOne({personcode: code})
.select('-_id -__v')
.populate('bookids', '-_id -__v')
.exec(function (err, data) {
//foo
});
Note that you should combine your field selections into a single string.

Thanks JohnnyHK, and for object parameter this works:
Entity.populate({
path: 'bookids',
// some other properties
match: {
active: true
},
// some other properties
select: '-_id -__v' // <-- this is the way
}).then(...) // etc

To exclude individually
User.findOne({_id: userId}).select("-password")
To exclude using the schema
var userSchema = mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
select: false,
},
});
or this will also work
db.collection.find({},{"field_req" : 1,"field_exclude":0});

I came searching for something slightly different. just in case someone needs same as me.
you can specify specific fields to auto-populate during creation of schema as shown below
const randomSchema = mongoose.Schema({
name: {type: String,trim: true},
username: {type: String,trim: true},
enemies: {
type: ObjectId,
ref: randomMongooseModel,
autopopulate:{
select: '-password -randonSensitiveField' // remove listed fields from selection
}
},
friends: {
type: ObjectId,
ref: randomMongooseModel,
autopopulate:{
select: '_id name email username' // select only listed fields
}
}
});
I am using mongoose-autopopulate plugin for this example

Related

Update properties that reference another model without duplication

I am trying to create an event list where users can add and remove themselves from events and specify if they are bringing guests with them to that event.
So I have an event schema and a user schema, where the event schema is referencing the user schema. So when a new event is created users can add themselves to that event with their ids.
Now I'm trying to make it so that users can also include guests. How Do I achieve that?
Here's an example
User Schema:
let UserSchema = new mongoose.Schema({
email: {
type: String,
lowercase: true,
unique: true,
required: true
},
name:{
type: String,
require: true
},
password: {
type: String,
required: true
},
...
Event Schema:
let EventSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title: {
type: String,
require: true
},
// Guest property is ignored
attending: [{
type: mongoose.Schema.Types.ObjectId,
guest: Number, //This is being ingored and never updated
ref: 'User'
}]
})
Second way of defining the relavant part in the schema:
...
//In this example the guest will be added but duplicates will occur
user:[{
guest: Number, // To make it clear this is not referencing anything it's just a number
attending: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}]
How do I update the type and guest properties with addToSet (to prevent duplication) in the above configuration?
Event.findOneAndUpdate({_id:req.body.eventId}, query)
I don't think you understand how mongoose schemas work, you might want to spend some more time on their documentation.
What you have provided as code is what appears to be a field called Events in your Schema which is an array of objects, each object of which has a single field called attending, which itself is required to be an ObjectId type and reference the 'User' collection. There is also a guest property on the field definition which will be ignored by Mongoose as it doesn't understand what you're asking for.
Realize that what this data structure is, is instructions to Mongoose on how to validate and persist your data. It won't generally be updated at runtime for most applications and will not store data directly, again its purpose is to give clues to Mongoose as to how you want the data stored.
/** Edit based on comments and updated question **/
As I said before, you can't directly embed another field into the definition of a field. What you can do is create a mixed type which has both pieces of information, but that will require you to manage things yourself to some degree.
let EventSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title: {
type: String,
require: true
},
attendees: [{
user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
guests : Number
}]
})
Anytime anyone is added to the attending list, you'll need to call event.markModified() to make sure it gets saved. If you don't want to allow duplicate users, you'll also need to check that. One way to make sure that happens is to populate() that field when you fetch the event, then just check locally for matches.
/** Edit #2 **/
You can also explicitly create another schema to 'hold' your user and # guests information, which will then create models that Mongoose will watch, and you can apply validation to them via normal Mongoose methods and not worry about dirty checking. That'd look like this:
// in ./models/attendee.js
let AttendeeSchema = new Schema({
user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
unique : true
},
guests : Number
}
mongoose.model('Attendee', AttendeeSchema);
// in your Events definition
let Attendee = mongoose.model('Attendee');
let EventSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title: {
type: String,
require: true
},
attendees: [Attendee]
})
/** Edit 3: Now, with queries **/
To insert a new attendee, given an existing event and a known user:
event.attendees.push(new Attendee({user: user, guests: 5}));
event.save(console.log);
To update an existing attendee, you'll need to find the one you're looking for first:
let attendee = event.attendees.find((attendee) => { return attendee._id.toString() === user._id.toString(); });
attendee.guests = 10;
event.save(console.log);

Populating a field which is a property of an embedded array in a single query

I'm writing a forum module using Mongoose & Nodejs.
I have a collection of ForumPost objects which have a property "comments" which is a mongoose schema containing a reference property "author", which references a User model. I'm having trouble populating the "author" property for each of the items in the "comments" array.
Here's the ForumPost object definition:
var ForumPost = db.model("ForumPost", {
author: { type: Schema.Types.ObjectId, ref: "User", required: true, default: null, select: true},
comments: [new db.Schema({
author: { type: Schema.Types.ObjectId, ref: "User" },
comment: { type: String, default: ""},
})]
});
When I pull these from the database, i'm populating the "author" field of the forum post, which works fine as it's a basic populate operation.
I've been trying to populate the "author" field of the comments array this morning to no avail, my latest attempt is posted below.
I use the following query:
ForumPost.findOne({alliance_id: alliance._id, _id: post_id})
.populate("author")
.populate({
path: 'comments',
populate: {
path: 'comments.author',
model: 'User'
}
})
.select("comments")
.exec(function(error, post) {
return res.json(post);
});
Is this possible to populate the "author" field of the objects in the comments array of ForumPost in this single query?
Because you're not doing any nested population, you can include both paths in a single populate call:
ForumPost.findOne({alliance_id: alliance._id, _id: post_id})
.populate('author comments.author')
.select('author comments')
.exec(function(error, post) {
return res.json(post);
});

mongoose document filtering properties

I have defined a schema like
var UserSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true },
location: { type: String, required: true },
picture: { type: String, required: true },
passwordHash: { type: String, required: true },
resetPasswordToken: String,
resetPasswordExpired: Boolean
});
I have a REST Endpoint which return list of all users. In that list I want to hide some properties i.e, passwordHash, resetPasswordToken, resetPasswordExpired
I defined a custom filter function like below
var doFilterUser = function(user) {
_.omit(user, ['passwordHash', 'resetPasswordToken', 'resetPasswordExpired']);
user.id = user._id;
delete user._id;
delete user.__v;
return user;
};
_ is lodash
When I check my API is responding with all user properties
This filter function is defined in common helper module and I am calling it like
User.findOne({_id: id}, function(err, user) {
var filtered = helper.doFilterUser(user);
});
How to resolve this issue?
Try this:
You are allowed to access certain values through mongoose.
User.findOne({_id: id}, 'firstName lastName email location picture', function(err, user){
console.log(user);
});
You just mention the fields needed, after the query.
Hope it helps....
The problem here is that you still have a mongoose document that conforms to s strict schema. If you want to change that document, then you need to make it a "raw" object without all the additional controls:
User.findOne({_id: id}, function(err, user) {
var filtered = helper.doFilterUser(user.toObject());
});
So the .toObject() method here will return an object in it's raw form. That allows you to manipulate the keys how you wish.
You can also explicitly direct it not to serve back certain properties. Useful if you don't want to render a hashed password over the wire. The find method would look like this:
User.find({}, '-id -__v',function(err,users){
})
or
User.findOne({_id: id}, '-id -__v',function(err,user){
})

Relational database design to mongoDB/mongoose design

I have recently started using mongoDB and mongoose for my new node.js application. Having only used relational databases before I am struggling to adapt to the mongoDB/noSQL way of thinking such as denormalization and lack of foreign key relationships. I have this relational database design:
**Users Table**
user_id
username
email
password
**Games Table**
game_id
game_name
**Lobbies Table**
lobby_id
game_id
lobby_name
**Scores Table**
user_id
game_id
score
So, each lobby belongs to a game, and multiple lobbies can belong to the same game. Users also have different scores for different games. So far for my user schema I have the following:
var UserSchema = new mongoose.Schema({
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
So my question is, how would I go about structing the relational design into mongoDB/mongoose schemas? Thanks!
EDIT 1
I have now tried to create all the schemas but I have no idea if this is the right approach or not.
var UserSchema = new mongoose.Schema({
_id: Number,
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
scores: [{ type: Schema.Types.ObjectId, ref: 'Score' }]
});
var GameSchema = new mongoose.Schema({
_id: Number,
name: String
});
var LobbySchema = new mongoose.Schema({
_id: Number,
_game: { type: Number, ref: 'Game' },
name: String
});
var ScoreSchema = new mongoose.Schema({
_user : { type: Number, ref: 'User' },
_game : { type: Number, ref: 'Game' },
score: Number
});
Mongoose is designed in such a way that you can model your tables relationally with relative ease and populate relational data based on the ref you defined in the schema. The gotcha is that you need to be careful with populating. If you populate too much or nest your populations you will run into performance bottle necks.
Your approach in Edit 1 is largely correct however you usually don't want to populate a remote ref based on a Number or set the _id of a model to a Number since mongo uses it's own hashing mechanism for managing the _id, this would usually be an ObjectId with _id implied. Example as shown below:
var ScoreSchema = new mongoose.Schema({
user : { type: Schema.Types.ObjectId, ref: 'User' },
game : { type: Schema.Types.ObjectId, ref: 'Game' },
score: Number
});
If for some reason you need to maintain a number id for your records consider calling it uid or something that won't conflict with mongo / mongoose internals. Good luck!
First of all, you are hitting on some good points here. The beauty of Mongoose is that you can easily connect and bind schemas to a single collection and reference them in other collections, thus getting the best of both relational and non-relational DBs.
Also, you wouldn't have _id as one of you properties, Mongo will add it for you.
I've made some changes to your schemas using the mongoose.Schema.Types.ObjectId type.
var UserSchema = new mongoose.Schema({
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
scores: [{ type: Schema.Types.ObjectId, ref: 'Score' }]
});
var GameSchema = new mongoose.Schema({
name: String
});
var LobbySchema = new mongoose.Schema({
_game: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Game'
},
name: String
});
var ScoreSchema = new mongoose.Schema({
_user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
_game : {
type: mongoose.Schema.Types.ObjectId,
ref: 'Game'
},
score: Number
});
This will allow you to query your database and populate any referenced collections and objects.
For example:
ScoreSchema.find({_id:##userIdHere##})
.populate('_user')
.populate('_game')
.exec(function(err, foundScore){
if(err){
res.send(err)
} else {
res.send(foundScore)
}
}
This will populate the related user and game properties.
As you edited the post, I think it would be good. At least not bad :)
Check Mongoose Query Population. It's very useful to get related data.
var mongoose = require('mongoose'),
ObjectId = mongoose.Schema.Types.ObjectId
// code, code, code
function something(req, res) {
var id = req.params.id
// test id
return Lobby.findOne({_id: new ObjectId(id)})
.populate('_game')
.exec(function(error, lobby) {
console.log(lobby._game.name);
});
}
Two ways (that I know of). You store an id (that is indexed) and once you query the first table, you then query the second table to grab info from that, as there are no joins. This means that if you grab say, user id's from one table, you will then need to make multiple queries to the user table to get the user's data.
The other way is to store it all in one table, even if it's repetitive. If all you need to store is for example, a user's screen name with something else, then just store it with the other data, even if it's already in the user table. I'm sure others will know of better/different ways.

Recreate models using Mongoose

I want to recreate the models in database after dropping everything in it.
Mongoose (or Mongo itself )actually recreates the documents but not the indices. So is there a way to reset Mongoose so that it can recreate indices as if running the first time?
The reason why I'm using dropDatabase is because it seems easier while testing. Otherwise I would have to remove all collections one by one.
While not recommended for production use, depending on your scenario, you can add the index property to a field definition to specify you want an index created:
var animalSchema = new Schema({
name: String,
type: String,
tags: { type: [String], index: true } // field level
});
animalSchema.index({ name: 1, type: -1 }); // schema level
Or,
var s = new Schema({ name: { type: String, sparse: true })
Schema.path('name').index({ sparse: true });
Or, you can call ensureIndex on the Model (docs):
Animal.ensureIndexes(function (err) {
if (err) return handleError(err);
});

Categories