how does the populate method works in mongoose? - node.js

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.

Related

Insert a document with mongoose without initialize the model with empty attributes

I want to insert a document in my database from a website form. I have a model created with mongoose and I want to save in the database only the attributes that contains data and I don't want to save empty attributes.
This is my model:
const localizationSchema = new Schema({
name: { type: String, required: true },
spins: [{ type: String }],
spinsForChild: [{ type: String }],
parent: { id: String, name: String },
localizationType: { type: String },
count: { type: Number, default: 0 },
countries: [{ id: String, name: String, cities: [{ id: String, name: String }] }]
});
const Localization = mongoose.model('Localization', localizationSchema);
When I try to save a new document, it creates in the database all attributes although I don't send it on my query.
Localization.create({
name: body.name,
localizationType: body.localizationType,
"parent.id": parent.id,
"parent.name": parent.name,
spins: spins,
spinsForChild: spinsForChild
}, function(err) {
if (err) return handleError(err);
res.redirect('/localizations');
});
This code, for example, inserts in the DB an empty array called "countries".
I tried to use strict: false in the model declaration but it didn't works.
You could use this answer.
But, thing you try to implement seems to be anti-pattern and can cause errors when you will try to use array update operators with undefined array. So, use it carefully.
Good luck!

How to populate a document inside an array inside another document?

Sorry if title looks complicated... I couldn't think of a better way to describing it.
My real case situation matches the following Schemes:
Collection1:
const mongoose = require('mongoose');
const itemSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
quantity: { type: Number, required: [true, 'Quantity is required.'] },
collection2: { type: mongoose.Schema.Types.ObjectId, ref: 'Collection2' }
}, { _id : false });
const collection1Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
imagePath: { type: String, required: [true, 'Image is required.'] },
items: [itemSchema]
});
module.exports = mongoose.model('Collection1', collection1Schema);
Note: itemsSchema is inside the collection1 file (and having no declared _id's) because they only exist for the Collection1 model (considering "quantity" and other fields I removed for simplification). This itemsScheme is not needed elsewhere as another collection.
Collection2:
const mongoose = require('mongoose');
const collection2Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'], unique: true }
});
module.exports = mongoose.model('Collection2', collection2Schema );
Note: Other properties (such as 'imagePath') were removed for simplification.
Now, this is the query I am trying to run:
Collection1.find()
.populate({
path: 'items',
populate: {
path: 'collection2', model: 'Collection2'
}
})
.then(...)
.catch(...);
And this is the error message I am getting when I run it:
Error fetching collection1: Cast to ObjectId failed for value "{
name: 'an item name',
quantity: 750
}" at path "_id" for model "Collection1"
The exact same error happens if I just run:
Collection1.find()
.populate('items')
.then(...)
.catch(...);
Maybe I cannot run .populate('items') because it has no declared model. If this is the case, how can I populate collection2 while querying collection1? Again, I cannot consider storing items in a separated collection.
But if I run:
Collection1.find()
.populate('collection2')
.then(...)
.catch(...);
I get the items, no errors, but it doesn't populate collection2. Well, it makes sense for the items because they're just an array of a block of properties inside collection1. But what about populating collection2?
Collection2 already has a few documents added, all with their _ids and other fields well filled. In the controller, I set _id: new mongoose.Types.ObjectId(), while creating a new document for both cases, Collection1 and Collection2.
In the front-end, I create a new document for Collection1, I add items, each item with a document from Collection2, and I save everything with no errors. I also confirmed everything is been properly saved (collection1 has list of items and each item an _id reference to collection2). The only problem is populating collection2 inside this array.
I have already tried restructuring everything with _ids (including itemScheme) and dropping all collections to test it again but no success.
I have been stuck with this problem for about three days now.
Is there any special property I should be setting for populate to make it work for this specific structure?
Thanks in advance...
populate('items')
This will not work as item is not a model.
What you want is following:
Collection1.find()
.populate('items.collection2')
.then(...)
.catch(...);
This will populate collection2 in all the array elements

Adding objecct into an array if object field is unique in MondoDB/Mongoose

This is my Schema:
let userSchema = new Schema({
email: { type: String, required: true },
password: { type: String, required: true },
books: [{ cover: String, title: String, link: String }],
});
I am pushing an object to the books array, only if that object does not already exist. I don't understand why this does not work: I am saying find the user, check the books array to ensure that title does not exist add the new object to books array. However duplicates still show up in my array.
note: The query is being executed inside a function which is passed model, user, and an object containing the book data called info hence the info.title.
model.findOneAndUpdate(
{ email: user.email, 'books.title': { $ne: 'info.title ' } },
{ $addToSet: { "books": { title: info.title, cover:
info.cover, link: info.link } } },
{ new: true },
(err, user) => {
if (err) throw err;
console.log(user);
}
)
Appreciate the direction
From MongoDB $addToSet documentation https://docs.mongodb.com/manual/reference/operator/update/addToSet/#behavior
If the value is a document, MongoDB determines that the document is a
duplicate if an existing document in the array matches the to-be-added
document exactly; i.e. the existing document has the exact same fields
and values and the fields are in the same order. As such, field order
matters and you cannot specify that MongoDB compare only a subset of
the fields in the document to determine whether the document is a
duplicate of an existing array element.
You can also find a good answer here MongoDB - $addToSet on a list of Embedded Document

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.

Mongoose: how to structure a schema with a SET of subdocuments (one unique field)?

I'm trying to make the following schema to work:
var FormSchema = new mongoose.Schema({
form_code: { type: String, unique: true },
...
});
var UserSchema = new mongoose.Schema({
...
submissions: [{
form_code: { type: String, unique: true },
last_update: Date,
questions: [{
question_code: String,
answers: [Number]
}]
}],
});
The rationale here is that a user can have many unique forms submitted, but only the last submission of each unique form should be saved. So, ideally, by pushing a submission subdocument when updating a user, the schema would either add the submission object to the set, or update the subdocument containing that form_code.
The following code doesn't work as desired (it pushes the new subdocument even if the form_code is already present):
User.findOneAndUpdate(
{ _id: user.id },
{ $addToSet: { submissions: submission_object } },
function (err, user) {
// will eventually have duplicates of form_code at user.submissions
}
);
The above schema clearly doesn't work, what must be changed to achieve that "upsertToSet"?

Resources