I'm trying to get mongoose populate to work.
I have two models one for orders and the other for the order items.
I'm doing this purely to understand populate.
I did have two files for this but I have now got everything in one file called main.js
main.js creates the 3 items in items model.
I then try to populate the order model with the 3 items.
Output for the query now gives the correct populated result.
That is to say the output from the line
console.log(JSON.stringify(orders, null, "\t");
is now:
[
{
"_id": "55d32e4594db780b1bbb4372",
"__v": 0,
"lines": [
{
"price": 2.4,
"quantity": 5,
"_id": "55d32e4594db780b1bbb436f",
"__v": 0
},
{
"price": 3.7,
"quantity": 7,
"_id": "55d32e4594db780b1bbb4370",
"__v": 0
},
{
"price": 1.2,
"quantity": 3,
"_id": "55d32e4594db780b1bbb4371",
"__v": 0
}
]
}
]
The database however is not populating.
Below is the main.js file
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mydb');
var Schema = mongoose.Schema;
var OrderSchema = new Schema({
lines: [{type: mongoose.Schema.Types.ObjectId, ref: 'Item'}]
});
var ItemSchema = new Schema({
price: Number,
quantity: Number
});
var Order = mongoose.model('Order', OrderSchema);
var Item = mongoose.model('Item', ItemSchema);
var itemArray = [
{price: 2.4, quantity: 5},
{price: 3.7, quantity: 7},
{price: 1.2, quantity: 3}
];
Item.create(itemArray, function(err) {
if (err) {
console.log('Error creating items: ', err);
}
var order = new Order();
Item.find({}, {_id: 1}, function(err, result) {
result.forEach(function(obj) {
order.lines.push(obj._id);
});
}).exec().then(function() {
order.save(function(err) {
if (!err) {
Order.find({})
.populate('lines')
.exec(function(err, orders) {
console.log(JSON.stringify(orders, null, "\t")); // output is populated the database is still not populating.
});
}
});
} );
});
It appears there is some confusion for what the populate method is for. Essentially populate is mongoose's version of a "join". It allows a query (or a document) to automatically retrieve and "join" any associated documents and returns the modified document(s). Under the hood for every referenced value in a document that is to be populated, the populate method will execute a separate query to fetch those associated documents (generally this is by ID but can be a custom query) and then in memory it will replace the references with the retrieved documents.
It does not alter the documents in the collection at all as this is purely a fetch operation.
Related
I am having below schema in my API which gets details of username and the products he added to the cart.
const mongoose = require('mongoose');
mongoose.connect('mongodb connection').then(()=>{
console.log('DB connection is successfull');});
const AutoIncrement = require('mongoose-sequence')(mongoose);
const cartSchema = new mongoose.Schema({
cartId : {
type : Number
},
username : {
type : String
},
productsInCart : [{
productId : {type : Number,required : true},
productName : {type:String},
quantity : {type:Number}
}],
statusOfCart : {
type : String,
default : 'Open'
}},{ timestamps: true });
cartSchema.plugin(AutoIncrement,{id : 'cart_seq',inc_field : 'cartId'});
let cartModel = mongoose.model('carts',cartSchema);
module.exports = cartModel;
As you can see in the above code I am also using the mongoose-sequence to make cartId as a auto-incremented field.
Now, I have a POST request which gets the below JSON in request body and adds it to the cart collection in the MongoDB using the create method.
{
"username":"admin",
"productsInCart":
[
{
"productId":1,
"productName":"Watch",
"quantity":4
},
{
"productId":2,
"productName":"Phone",
"quantity":5
}
]
}
The code inside the Route Handler for the POST request in Express API would look something like this
let ctMod = new cartModel();
ctMod.username = req.body.username;
ctMod.productsInCart = req.body.productsInCart;
let insCartData = await cartModel.create(ctMod,{new:true});
if(insCartData.length > 0)
{
return res.status(200).json({
message : `New items got inserted into the cart with the ID : ${insCartData.cartId}`,
data : insCartData
});
}
The above code inserts two entries into the collection like below instead of one
{
"statusOfCart": "Open",
"productsInCart": [],
"createdAt": "2021-01-04T15:25:35.188Z",
"updatedAt": "2021-01-04T15:25:35.188Z",
"cartId": 13,
"__v": 0
},
{
"statusOfCart": "Open",
"productsInCart": [
{
"_id": "5ff332a891aa170b60a21ea9",
"productId": 1,
"productName": "Watch",
"quantity": 4
},
{
"_id": "5ff332a891aa170b60a21eaa",
"productId": 2,
"productName": "Phone",
"quantity": 5
}
],
"username": "admin",
"createdAt": "2021-01-04T15:25:35.210Z",
"updatedAt": "2021-01-04T15:25:35.210Z",
"cartId": 14,
"__v": 0
}
can you help me understand why there is duplicate entries in my db?
I'm not sure why you are using cartModel.create(ctMod,{new:true}); inorder to create a new entry to your collection.
You can simply do like this :
let ctMod = new cartModel();
ctMod.username = req.body.username;
ctMod.productsInCart = req.body.productsInCart;
try{
let insCartData = await cartModel.save();
return res.status(200).json({
error:false,
message : `New items got inserted into the cart with the ID : ${insCartData.cartId}`,
data : insCartData
});
}
catch(err){
console.error("error inserting data", err);
return res.status(500).json({error:true, message:"Something went wrong :("}).
}
UPDATE :
Reason for the duplicate entry is that you are passing the {new:true} flag in the create API. It's used to return the updated document in the findOneAndUpdate method. But in create/save, the mongoose return the data by default. Hence the flag is not needed.If you omit the flag, duplicate entries can be prevented.
Also, as you are using the create method to save the new entry, you need not create an instance of the cartModel. ie.,
let ctMod = new cartModel();
can be just
let ctMod = {}
Hope it helps!
I can't push items into MongoDB array every time that i try to push a new element it creates an empty object and i cant figure out why,
I already used the
Collection.Array.push({element})&
Collection.save()
but i cant figure out a solution
This is My Schema
const Schema = mongoose.Schema;
var ParticipantSchema = new Schema({
nom:{Type:String},
prenom:{Type:String},
email:{Type:String}
})
var CompetitionSchema = new Schema({
nom:String,
date:Date,
place:String,
participant :[ParticipantSchema]
})
module.exports = mongoose.model("Competition",CompetitionSchema);
This is my funtion
exports.addParticipant=function(req,res){
var newParticipant={
"nom":req.body.nom,
"prenom":req.body.prenom,
"email":req.body.email
}
Competition.updateOne(
{ _id:req.body.id},
{ $push: { participant: newParticipant } },
(err,done)=>{
return res.json(done)
}
);
}
the result is always an empty object like below
{
"_id": "5ded0eeb85daa100dc5e57bf",
"nom": "Final",
"date": "2019-01-01T23:00:00.000Z",
"place": "Sousse",
"participant": [
{
"_id": "5ded0eeb85daa100dc5e57c0"
},
{
"_id": "5dee3c1b08474e27ac70672e"
}
],
"__v": 0
}
There is no problem in your code, the only problem is that in schema definition you have Type, but it must be type.
If you update your ParticipantSchema like this, it will work:
var ParticipantSchema = new Schema({
nom: { type: String },
prenom: { type: String },
email: { type: String }
});
You are using another Schema in the Array. This results in so-called subdocuments (https://mongoosejs.com/docs/subdocs.html). Mongoose does not populate subdocuments by default. So all you see is just the _id. You can use the populate method to see all subdocuments in detail. ( https://mongoosejs.com/docs/populate.html ) .
Example :
Competition.
find({}).
populate('participant').
exec(function (err, comps) {
//
});
You can either use populate on the Model or on the Document. For populating a document, take a look at https://mongoosejs.com/docs/api.html#document_Document-populate . There is also a auto-populate plugin available via npm but in most cases it's not necessary : https://www.npmjs.com/package/mongoose-autopopulate .
I am creating a schema for an Order model that will track the items ordered along with the quantity purchased. I want to keep the itemId references and the quantity tied together as an array in one parameter.
I have created an Array that includes a reference to the ObjectId plus an additional Number type. I am currently unable to populate the product information using a .populate() query.
Order Schema
const mongoose = require("mongoose");
const { Schema } = mongoose;
const orderSchema = new Schema({
orderNumber: String,
_itemsOrdered: [
{
itemId: {
type: mongoose.Schema.Types.ObjectId,
ref: "menuItems"
},
quantity: Number
}
]
});
mongoose.model("orders", orderSchema);
MenuItem Schema
const mongoose = require("mongoose");
const { Schema } = mongoose;
const MenuItemSchema = new Schema({
imageURL: String,
name_en: String,
name_es: String,
type_en: String,
type_es: String,
description_en: String,
description_es: String,
dietaryCallouts: [String],
price: Number
});
mongoose.model("menuItems", MenuItemSchema);
module.export = MenuItemSchema;
I am able to save the record but cannot populate the MenuItem information with the following query:
Order Controller
async show(req, res, next) {
try {
const orderId = req.params.id;
let order = await Order.findById({ _id: orderId }).populate(
"_itemsOrdered.itemId"
);
res.send(order);
} catch (err) {
res.status(402).send(err);
}
}
Here it the order object that is being saved to the DB.
Order Object
{
"_id": "5dc93b9c0085b8045e0c8aa3",
"orderNumber": "Order 3",
"_itemsOrdered": [
{
"_id": "5dc93b9c0085b8045e0c8aa5",
"itemId": "5dc7f814a2679b47319a79a4",
"quantity": 1
},
{
"_id": "5dc93b9c0085b8045e0c8aa4",
"itemId": "5dc7e5c7de590744c46f93da",
"quantity": 2
}
],
"__v": 0
}
Your order schema must be like this:
const orderSchema = new Schema({
orderNumber: String,
_itemsOrdered: [
{
itemId: { type: mongoose.Schema.Types.ObjectId, ref: "menuItems" },
quantity: Number
}
]
});
And you can use the following route to create an order document.
router.post("/order", async (req, res, next) => {
try {
const { orderNumber, _itemsOrdered } = req.body;
let order = new Order({ orderNumber, _itemsOrdered });
order = await order.save();
res.status(201).send(order);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
});
Sample body: (you need to change ids according to yours)
{
"orderNumber": "Order 1",
"_itemsOrdered": [
{"itemId": "5dc90346222b892434e4675a", "quantity" : 1 },
{"itemId": "5dc90359222b892434e4675b", "quantity" : 2 }
]
}
To get the order and its items you can use populate like this:
router.get("/orders/:id", async (req, res) => {
try {
const orderAndItems = await Order.findById(req.params.id).populate(
"_itemsOrdered.itemId"
);
res.send(orderAndItems);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
});
This will give you a result like this:
{
"_id": "5dc904db8407a217b4dfe6f4",
"orderNumber": "Order 1",
"_itemsOrdered": [
{
"_id": "5dc904db8407a217b4dfe6f6",
"itemId": {
"_id": "5dc90346222b892434e4675a",
"name_en": "item 1",
"price": 1,
"__v": 0
},
"quantity": 1
},
{
"_id": "5dc904db8407a217b4dfe6f5",
"itemId": {
"_id": "5dc90359222b892434e4675b",
"name_en": "item 2",
"price": 2,
"__v": 0
},
"quantity": 2
}
],
"__v": 0
}
I am trying to query an embedded subdocument and then only return an array in that subdocument via projection. After a query you can select fields that you want returned via projection. I want to use the native functionality because it is possible and the most clean way. The problem is it returns arrays in two documents.
I tried different query and projection options, but no result.
User model
// Define station schema
const stationSchema = new mongoose.Schema({
mac: String,
stationName: String,
syncReadings: Boolean,
temperature: Array,
humidity: Array,
measures: [{
date: Date,
temperature: Number,
humidity: Number
}],
lastUpdated: Date
});
// Define user schema
var userSchema = mongoose.Schema({
apiKey: String,
stations : [stationSchema]
}, {
usePushEach: true
}
);
api call
app.get('/api/stations/:stationName/measures',function(req, res, next) {
var user = {
apiKey: req.user.apiKey
}
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
const options = {
'stations.$.measures': 1,
}
User.findOne(query, options)
.exec()
.then(stations => {
res.status(200).send(stations)
})
.catch(err => {
console.log(err);
res.status(400).send(err);
})
});
Expected result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"measures": [
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:45.468Z",
"_id": "5c3a6f09fd357611f8d078a0"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:46.500Z",
"_id": "5c3a6f0afd357611f8d078a1"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:47.041Z",
"_id": "5c3a6f0bfd357611f8d078a2"
}
]
}
]
}
Actual result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"measures": [
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:45.468Z",
"_id": "5c3a6f09fd357611f8d078a0"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:46.500Z",
"_id": "5c3a6f0afd357611f8d078a1"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:47.041Z",
"_id": "5c3a6f0bfd357611f8d078a2"
}
]
},
******************************************************
// this whole object should not be returned
{
"stationName": "office",
"measures": []
}
******************************************************
]
}
edit
The answer below with aggregation works, but I still find it odd that I would need so much code. If after my normal query I get the same result with ".stations[0].measures", instead of the whole aggregation pipeline:
.then(stations => {
res.status(200).send(stations.stations[0].measures)
})
The way I read the code, the above does exactly the same as:
const options = {'stations.$.measures': 1}
Where the dollar sign puts in the index 0 as that was the index of the station that matches the query part: stationName: "livingroom"
Can someone explain?
This is not described in terms of mongoose but this will find a particular station name in an array of stations in 1 or more docs and return only the measures array:
db.foo.aggregate([
// First, find the docs we are looking for:
{$match: {"stations.stationName": "livingroom"}}
// Got the doc; now need to fish out ONLY the desired station. The filter will
// will return an array so use arrayElemAt 0 to extract the object at offset 0.
// Call this intermediate qqq:
,{$project: { qqq:
{$arrayElemAt: [
{ $filter: {
input: "$stations",
as: "z",
cond: { $eq: [ "$$z.stationName", "livingroom" ] }
}}, 0]
}
}}
// Lastly, just project measures and not _id from this object:
,{$project: { _id:0, measures: "$qqq.measures" }}
]);
$elemMatch operator limits the contents of an array field from the query results to contain only the first element matching the $elemMatch condition.
Try $elemMatch in Select Query as below :
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
const options = {
'stations' : {$elemMatch: { 'stationName' : req.params.stationName }}
}
I have 2 schemas set up in mongoose:
var Job = new mongoose.Schema({
title: String,
budget: Number
});
var JobModel = mongoose.model('Job', Job);
var Customer = new mongoose.Schema({
name: String,
jobs: [Job]
});
var CustomerModel = mongoose.model('Customer', Customer);
The Customer model has an array of job models.
I am adding a new job as follows:
app.post('/api/jobs', function(req, res){
var job = new JobModel({
title: req.body.title,
budget: req.body.budget
});
job.save(function(err){
if(!err){
CustomerModel.findById(req.body.customerId, function(err, customer){
if(!err){
customer.jobs.push(job);
customer.save(function(err){
if(!err){
return console.log('saved job to customer');
}
});
}
});
return console.log('created job');
} else {
return console.log(err);
}
});
return res.send(job);
});
When I add a new job and GET all the customers I get which I think is correct:
[{
"__v": 1,
"_id": "50f85695771aeeda08000001",
"name": "Customer1",
"jobs": [
{
"_id": "50fad6985edd968840000002",
"budget": 100,
"title": "job1"
}
]
}, ...]
Now if I update job1, and GET all the jobs, job 1 has been updated (budget is now 500)
[{
"title": "job1",
"budget": 500,
"_id": "50fad6985edd968840000002",
"__v": 0
}, ...]
but the job1 in the customers job array remains unchanged.
[{
"__v": 1,
"_id": "50f85695771aeeda08000001",
"name": "Customer1",
"jobs": [
{
"_id": "50fad6985edd968840000002",
"budget": 100,
"title": "job1"
}
]
}, ...]
Do I therefore need to search the Customer1's job array and find the job1 job and update that also every time I update or delete or am I totally doing the whole nesting thing the wrong way?
I get that the jobs array in customer is an array of objects but I thought that they may be some how just a reference copy of the job rather than a duplicate??
If you use an embedded array of Job documents as you are here, they're completely independent copies and it's up to you to keep them in sync with the separate jobs collection.
The alternative is to have the jobs field of Customer contain an array of ObjectId references to 'Job' and then use Mongoose's query population to populate them on-demand, as needed.
jobs: [{type: Schema.Types.ObjectId, ref: 'Job'}]