Mongoose search from populated document - node.js

I have this simplified Mongoose Schema:
UserSchema = new db.Schema({
_id : { type: db.Schema.Types.ObjectId,
name : { type : String },
friends : [{ type: db.Schema.Types.ObjectId, ref: 'User' }]
})
I want to get all friends with name that starts with a certain key string, let's say 'Pe' should return records 'Peter' and 'Petra'.
So I tried populating the friends field first:
user.Model
.findOne({
_id : loggedId,
})
.select('friends')
.populate('friends', 'name')
.exec(function(err, results) {
console.log(results);
});
That will return all the user's friends and their name which is not really what I want.
How do I return only those with a name that starts with certain characters?
TIA.

You can add options to the populate call.
See http://mongoosejs.com/docs/api.html#model_Model.populate

Related

Mongoose Model ObjectId References Not Working

I'm working with Mongoose models and references. I've been using the code from mongoose's website where it talks about the populate method and references. I am trying to have it save the respective "referenced" ids in both models. It is only saving the reference ids in the story model. Here is the code:
Update: Added schemas at the top to help:
var personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);
(end of schemas)
var author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});
author.save(function (err) {
if (err) return handleError(err);
var story1 = new Story({
title: 'Casino Royale',
author: author._id // assign the _id from the person
});
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
});
When you run this code, it generates this in mongo:
db.people.find()
{ "_id" : ObjectId("5be0a37f1dd61a343115e2c8"), "stories" : [ ], "name" : "Ian Fleming", "age" : 50, "__v" : 0 }
db.stories.find()
{ "_id" : ObjectId("5be0a37f1dd61a343115e2c9"), "title" : "Casino Royale", "author" : ObjectId("5be0a37f1dd61a343115e2c8"), "__v" : 0 }
It appears to not be storing any ids in the people collection within "stories." Wouldn't you want to save the stories ids in the people collection as well?
I tried to modify the code to make it work with (moved the author save function, until after the story id is set):
var author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});
var story1 = new Story({
_id: new mongoose.Types.ObjectId(),
title: 'Casino Royale',
author: author._id // assign the _id from the person
});
author.stories = story1._id;
author.save(function (err) {
if (err) return handleError(err);
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
This gives me an author undefined.
Mongo wouldn't automatically add to the Person "stories" field just because you added a Story object.
You don't really need to store the story ids in Person objects anyway, as you can always get a list of stories by an author with
db.stories.find({author: <id>})
Storing in both places would create redundant information and you'd have to pick one to be the truth in the case of a mismatch. Better to not duplicate, methinks.
UPDATE:
References appear to help you populate referenced fields in queries automatically. According to this post you can retrieve an author and their stories like this:
db.persons.find({_id: <id>}).populate('stories')
Haven't personally used this but it looks pretty handy.
Mongoose docs for populate: https://mongoosejs.com/docs/populate.html

Retrieving ID of subdocument after update in MongoDB

I wrote a service to push a new data to my collection using update statement and I need to retrieve the id of last inserted data. I am using db.collection.update for this, but it just giving a response like this:
{
"result": {
"ok": 1,
"nModified": 1,
"n": 1
}
}
My api for that is:
app.post('/feeds',function(req,res) {
var _id = req.body._id;
var title = req.body.title;
var description = req.body.description;
var latitude = Number(req.body.latitude);
var longitude = Number(req.body.longitude);
db.user.update(
{_id:_id },
{$push : {
feed:{
title: title,
description:description,
latitude:latitude,
longitude:longitude
}
}
},function (err,result) {
if (err) {
res.json({"success": '0', "message": "Error adding data"});
}
else {
res.json({'result':result});
}
});
});
This is my Mongoose schema:
var user = new mongoose.Schema({
username : {type: String},
email : {type: String,index: {unique: true}},
password : {type: String},
feed : [{
title : {type: String},
description : {type: String},
latitude : {type:Number},
longitude : {type:Number},
feedImages : [{
imageUrl: {type: String}
}],
}]
});
I want to add data to feed.
My database structure
I want the id of newly pushed feed.
Embedded way (current schema)
If you want to use one document per feed and pushing sub-documents into (embedded way), you can pre-assign a _id to your newly created feed sub-document:
var feedId = new ObjectId();
db.user.update({
_id: _id
}, {
$push: {
feed: {
_id: feedId,
title: title,
description: description,
latitude: latitude,
longitude: longitude
}
}
}, function(err, result) {
/* ...your code... */
});
Don't forget to add a index for user.feed._id if you want to query for this field (using ensureIndex orcreateIndex depending of your mongodb version).
Using separate collection
As alternative way, you can use a separate feed collection, and use insert statements including a userId foreign key.
You can find more about pro/cons using Multiple Collections vs Embedded Documents here.
You should use db.user.insert() , why do you need the id, when you already have it from the req body?
db.user.insert(objectToInsert, function(err,docsInserted){
console.log(docsInserted);
});
It's a good case for findOneAndUpdate.
Finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callback. The query executes immediately if callback is passed.
Available options
new: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)

Unique and Sparse Schema-level Index MongoDB and Mongoose

I am trying to create an index on two fields of a schema that are to be unique and sparse in MongoDB using Mongoose as follows:
var ArraySchema = new Schema ({
user_id: {type: mongoose.Schema.Types.ObjectId, ref:'User'},
event_id: {type: mongoose.Schema.Types.ObjectId, ref:'Event'}
}, {_id:false});
ListSchema.index({user_id:1, event_id:1}, {sparse:true, unique:true});
Which is then used in an array in the User schema as such:
var User = new Schema({
arrayType1 : {
type: [ArraySchema]
},
arrayType2 : {
type: [ArraySchema]
},
arrayType3 : {
type: [ArraySchema]
}
//More specifications for user schema...
});
However, when trying to save multiple users without the array field, errors are thrown for duplicate fields. The error in Mocha looks similar to this: array.event_id_1 dup key {null, null}. An example of a segment of code that would throw this error is as follows:
var user1, user2;
user1 = new User({
username : 'username1',
password : 'password'
});
user2 = new User({
username : 'username2',
password : 'password'
});
user1.save(function() {
user2.save();
});
Here is my reasoning behind making the the fields of ArraySchema unique and sparse: If the array field is specifed, I do not want the array to contain duplicate objects; however, the array field is not required, so there will be many Users that have null for this field. Obviously I cannot use field-level indices since there are multiple fields that would need an index (arrayType1, arrayType2, arrayType3).
It appears that doing this sort of thing is not supported, at least at this time. The alternative would be to create a compound index on these fields then whenever adding a new element to the field use user.arrayType1.addToSet(). Here is an example of how this would work:
ArraySchema:
var ArraySchema = new Schema ({
user_id: {type: mongoose.Schema.Types.ObjectId, ref:'User'},
event_id: {type: mongoose.Schema.Types.ObjectId, ref:'Event'}
}, {_id:false});
ListSchema.index({user_id:1, event_id:1});
User schema:
var User = new Schema({
arrayType1 : {
type: [ArraySchema]
},
arrayType2 : {
type: [ArraySchema]
},
arrayType3 : {
type: [ArraySchema]
}
//More specifications for user schema...
});
Then I could declare new users as usual (as I did in the question); however, when I want to add a new element to arrayType1, for example, I would use the following line of code to add to new element only if it is not already there:
user.arrayType1.addToSet({user_id : user2._id, event_id : event._id});
user.save(function(err, result) {
//Callback logic.
};
Where user2 and event are defined earlier in the code and saved to the db. Alternatively I could use Mongoose's update function as such:
User.update({_id : user._id}, {
$addToSet : {
arrayType1 : {
user_id : user2._id,
event_id : event._id
}
}
}, function(err) {
//Callback logic.
}
);

How can I save multiple items to an array field when creating a new record in MongoDB?

I have the following schema that is used for a "Groups" collection. I want to be able to create this record and push an arbitrary number of "members" to this group when it is first created. I am unable to get the "members" field to populate when I save the record. All other fields are saved without a problem.
var groupSchema = mongoose.Schema({
creator : String,
name : String,
members: [{
type: String,
ref: 'User'
}],
created : {
type: Date,
default: Date.now
}
});
app.post('/create-group', function(req, res) {
//req.body.users = ['12345', '23456', '34567'] for example
var group = new Group({
name : req.body.group_name,
creator : req.user._id,
members: {$push: req.body.users}
});
group.save(function (err, data) {
if (!err) {
return res.json(data);
}
});
});
No results are ever stored in "members", even though all other fields are saved correctly. What am I doing wrong?
EDIT
In your schema when you wrote ref :"User" it means that you have to provide a User schema and not a string or integer. If you just want an array of string you can simply use [String].
According to the doc you have to use an objectID http://mongoosejs.com/docs/populate.html
members: [{ type: Schema.Types.ObjectId, ref: 'User' }]
In the link provided above you will be able to check how to save your group and adding an arbitrary number of members.
members: [{ type: Schema.Types.ObjectId, ref: 'User' }]
As Su4p has pointed out. Don't worry about req.body.users containing strings instead of ObjectIds. mongoose will cast the strings into objectIDs.
There's also another mistake,
members: {$push: req.body.users}
should be
members: req.body.users
$push is an update operator. It's not meant for assigning arrays.

how does the populate method works in mongoose?

I have the following schemas defined:
module.exports.contact=Schema({
_id:Number,
name: String,
email: String,
contactNumber: String,
company:String,
_invoices:[{ type: Schema.Types.ObjectId, ref: 'invoice' }],
},{strict:false});
module.exports.invoice=Schema({
_contact: { type : Number, ref: 'contact'},
_id:Number,
invoiceNumber:Number,
},{strict:false});
And following data is inserted in mongodb:
//CONTACT COLLECTION
{_id:1,name:"Mrinal Purohit",email:"mrinal#mrinalpurohit.com",contactNumber:"+919016398554",company:""}
//INVOICE COLLECTION
{ _id:1, invoiceNumber:3 , _contact:1 }
Only one document is there in the respective collections. Now i attempt this:
models.contact.find().populate('_invoices').exec(function(err,data){
console.log(data);
res.send(data)
});
I get the following:
[ { _id: 1,
name: 'Mrinal Purohit',
email: 'mrinal#mrinallabs.com',
contactNumber: '+919016398554',
company: '',
__v: 0,
_invoices: [] } ]
I actually expected the invoices to be populated in the array. Is there something wrong with the schema definition?
I am unable to completely understand the .populate function of mongoose :(
Your _id type is mismatched
In your invoice schema _id is defined as a Number. However in your definition of _invoices in the contact schema, you've selected the Schema.Types.ObjectId type
Try changing the type of _invoices to Number, e.g.
module.exports.contact=Schema({
_id:Number,
name: String,
email: String,
contactNumber: String,
company:String,
_invoices:[{ type: Number, ref: 'invoice' }],
},{strict:false});
Alternatively, you can let Mongoose set _id for you by omitting the _id property in your schemas (so you can leave _invoices unchanged). It would save you a bit of work in generating unique IDs and it also has a bunch of other useful properties (like the embedded timestamp)
Update
damphat is also correct in pointing out the other error (which I missed). According to the data you're inserting, you've haven't pushed the _id of your new invoice into the relevant contact document
Here's a sketch of how to create an invoice while properly updating the relevant contact
Invoice.create(invoiceAttributes, function (err, invoice) {
if (err) // Handle your error
Contact.findOne({ _id: invoiceAttributes._contact}, function (err, contact) {
if (err) // Handle your error
contact._invoices.push(invoice._id); // The important step you were missing
contact.save(); // Insert your callback
});
});
Just add 1 line _invoices: [1] to the contact document:
{
_id: 1,
name: "Mrinal Purohit",
email: "mrinal#mrinalpurohit.com",
contactNumber: "+919016398554",
company: "",
_invoices: [1]
}
and correct the typo in your contact schema as well.

Resources