$ne query not working with mongoose but working in mongoshell - node.js

When I execute this mongoose query
FinancedProject.find({_id:{$ne:fb.financedProjects.financedProjectId}).exec( callback);
where fb is an object like this
{
_id: ObjectId("54das4da9dsa9d4ad4a9");
name: "some",
financedProjects: [
{registry:"147", financedProjectId:ObjectId("13da4sd4sa48da4dsa")},
{registry:"189", financedProjectId:ObjectId("5d5asd5a4sd5ada5sd")}
]
{
the result is undefined and when I execute it in the mongoshell the results are the expected

Because financedProjects is an array you have to address the element with [] like:
FinancedProject.find({
_id: {
$ne: fb.financedProjects[0|.financedProjectId
}
}).exec( callback );
EDIT:
mongoose ist JavaScript, so it follows the rules of JavaScript. fb.financedProjects is an array. So if you use the expression fb.financedProjects.financedProjectId this is evaluated to undefined by the JavaScript interpreter, because there is no financedProjectId property within that array (arrays have 0,1,2,3,... as properties). So mongoose does get { $ne: undefined } and has no chance to recognize that you meant the property financedProjectId of the array elements.
To achieve what you want, you can do this:
var arr = [];
for( var i=0; i<fb.financedProjects.length; i+=1 ) {
arr.push( fb.financedProjects[i|.financedProjectId );
}
FinancedProject.find({
$not: {
_id: {
$in: arr
}
}
}).exec( callback );

Related

Using updateOne method to update an object field inside array - throws error "Cannot create field 'url' in element"

I have MongoDB database (with Mongoose) containing a collection of Products (among others), which looks like this:
[
{
name: 'Product A',
url: 'product-a',
category: 'accesory',
price: 12,
shortDescription: ['example description'],
technicalSpecs: [{ speed: 10, weight: 20 }],
images: [],
reviews: [],
relatedProducts: [
{
url: 'product-b',
name: 'Product B',
// to be added in Update query
//id: id_of_related_product
}
]
} /* other Product objects */
]
As every MongoDB document is provided with _id property by default, but within the relatedProducts array i only have url and name properties, i want to add the id property (associated with corresponding Product) for each object in the relatedProducts array, so i will be able to conveniently query and process those related products.
I came up with an idea to query all Products to get only those, which have non-empty relatedProducts array. Then i loop them and i search for Product model, which has specific url and name properties - this let's me get it's true (added by MongoDB) _id. At the end i want to add this _id to matching object inside relatedProducts array.
My code:
async function assignIDsToRelatedProducts(/* Model constructor */ Product) {
const productsWithRelatedOnes = await Product.find(
{ relatedProducts: { $ne: [] }}, ['relatedProducts', 'name', 'url']
);
for (const productItem of productsWithRelatedOnes) {
for (const relatedProduct of productItem.relatedProducts) {
const product = await Product.findOne(
{ url: relatedProduct.url, name: relatedProduct.name },
'_id'
);
// throws error
await productItem.updateOne(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': product._id } }
);
}
}
}
However it throws the following error:
MongoError: Cannot create field 'url' in element {relatedProducts: [ /* array's objects here */ ]}
I don't know why MongoDB tries to create field 'url', as i use it to project/query url field (not create it) in updateOne method. How to fix this?
And - as i am newbie to MongoDB - is there a simpler way of achieving my goal? I feel that those two nested for..of loops are unnecessary, or even preceding creation of productsWithRelatedOnes variable is.
Is it possible to do with Mongoose Virtuals? I have tried it, but i couldn't match virtual property within the same Product Model - attach it to each object in relatedProducts array - after calling .execPopulate i received either an empty array or undefined (i am aware i should post at-the-time code of using Virtual, but for now i switched to above solution).
Although i didn't find solution or even reason of my problem, i solved it with a slightly other approach:
async function assignIDsToRelatedProducts(Product) {
const productsHavingRelatedProducts = Product.find({ relatedProducts: { $ne: [] }});
for await (const withRelated of productsHavingRelatedProducts) {
for (const relatedProductToUpdate of withRelated.relatedProducts) {
const relatedProduct = await Product
.findOne(
{ url: relatedProductToUpdate.url, name: relatedProductToUpdate.name },
['url', '_id']
);
await Product.updateMany(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': relatedProduct._id } }
);
}
}
const amountOfAllProducts = await Product.find({}).countDocuments();
const amountOfRelatedProductsWithID = await Product
.find({ 'relatedProducts.id': { $exists: true } }).countDocuments();
console.log('All done?', amountOfAllProducts === amountOfRelatedProductsWithID);
}
Yet, i still suppose it can be done more concisely, without the initial looping. Hopefully somebody will suggest better solution. :)

How can I get the length of postIds from querying my mongodb am using mongoose

My query returns this
[ { postIds:
[ 'https://www.facebook.com/BGISaddis/photos/a.245087592543292/597050737346974/?type=3&__xts__%5B0%5D=68.ARBQb6G1JvudtWmy9B4q_1gU29zEFw6CL6rcyuzJzi6PIlEZsBuc0qOn-YlsWlYif39ANPo22eQZBHvbNid84enU80cZ3K7PAIVB7f7kufQEcsdsYmPXkMUsj_kDgWOrVn6LLAl1SrLjTsaidmaFp1iJNgUwdD2lcXdf6MuPSaMhmm5JhC4_N-fiKTIcgmGwmRs6QmuJqoAMaGthI86M21OTK2VpaJ1_Nnkz8ix5FI4aArPHDKaDNyC_iRJ_wSQJsIOJn66cHCykz00pgW0fJvfCN0vgAAQwtk2uQ_cX3wJU53cuYJkO88ngrZjzH-PBygXCHQmqGnz1Y1zxEjZll6Q&__tn__=-R',
'https://www.facebook.com/BGISaddis/photos/a.154146514970734/154145898304129/?type=3&__xts__%5B0%5D=68.ARDT-LUHFL9tKF7cZH9QVdS66xERPVvnZDNSZecJW5lT4dNnyeDJ_OzGe7s6q3_adeQGlicbBsWv61lKlGfP13QWdQKHfL1RgNHHSxEKphglroMNPGFLEYSx6Fjhh0tLA1xXEYqOAOOQ5fV_AKcIZzj3dRB_IsZAWdxOpxkf--pqkyqbV5vwrgwTA56pJ8ZKWnNMyC2Je-3FG_8EPJiHO2kcWa5QxNfoZgv_Nx7mDRzhkpHlebP4aZfnEaLU-kJQdYlb2S55O5aVSMAPtDw3wJka7-mWPA&__tn__=-R',]
_id: 5e4bb430878c2365027fda2b } ]
This is My code
async function getPostLinks() {
const posts = await Post.find().select('postIds')
console.log(posts.length)
}
this code returns undefined
Use the .aggregate of mongoose with size aggregation.

Modify mongoose response data

I have a an API call that returns all messages a user has recieved as JSON.
The model data looks something like this:
{
sender: ObjectId,
reciever: ObjectId,
message: String
}
What is the proper way to modify the JSON the API responds with?
I want to end up with data grouped like so:
{
<senderid>:[ all of the messages from this sender],
<other_sender>:[ all of the messages from this sender]
}
Do I have to manually do this in javascript, or is there a faster way to do this taking advantage of mongoose?
Using the aggregation framework will be ideal for this task. You could run the following aggregation pipeline that makes use of the $group operator step to group the data to process them. The group pipeline operator is similar to the SQL's GROUP BY clause. In SQL, you can't use GROUP BY unless you use any of the aggregation functions. The same way, you have to use an aggregation function in MongoDB as well. In this instance, use the $push accumulator operator to create the array of messages.
Since Model.aggregate() returns plain objects you would then transform the resulting array to the desired hash key using lodash library's _.indexBy() method:
var pipeline = [
{
"$group": {
"_id": "$sender",
"messages": { "$push": "$message" }
}
}
];
Model.aggregate(pipeline,
function(err, res) {
if (err) return handleError(err);
var hashmap = _.chain(res)
.indexBy('_id')
.mapValues('messages')
.value();
console.log(JSON.stringify(hashmap, undefined, 4));
}
);
// Or use the aggregation pipeline builder.
Model.aggregate()
.group({ "_id": "$sender", "messages": { "$push": "$message" } })
.exec(function (err, res) {
if (err) return handleError(err);
var hashmap = _.chain(res)
.indexBy('_id')
.mapValues('messages')
.value();
console.log(JSON.stringify(hashmap, undefined, 4));
});
Check the demo below.
var data = [
{ _id: 'user1', messages: ['msg1', 'msg2'] },
{ _id: 'user2', messages: ['msg3', 'msg1'] },
{ _id: 'user3', messages: ['msg6', 'msg3'] },
{ _id: 'user4', messages: ['msg4', 'msg8'] }
];
var hashmap = _.chain(data)
.indexBy('_id')
.mapValues('messages')
.tap(log)
.value();
function log(value) {
pre.innerHTML += JSON.stringify(value, null, 4) + "\n"
}
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<pre id="pre"></pre>

MongoDB and NodeJS get related data from 3 collections

i have a mongoDB query to get data with $group and $count.
This data contains the _id from other documents collection.
How can i get the other documents by its _id in NodeJS and MongoDB asyncrohnous?
db.orders.aggregate([
{$match: { 'works.TechnicianId': {$in:['53465f9d519c94680327965d','5383577a994be8b9a9e3f01e']},
'works.Date': {$gte: ISODate("2013-05-21T06:40:20.299Z"), $lt: ISODate("2016-05-21T06:40:20.299Z")}}},
{$unwind: "$works" },
{$group: {_id: "$works.TechnicianId",total:{$sum:'$works.price'},ordersId: { $push: "$_id" }}},
])
This is the result:
{
"result" : [
{
"_id" : "53465f9d519c94680327965d",
"total" : 198,
"ordersId" : [
ObjectId("537b5ea4c61b1d1743f4341f"),
ObjectId("537b4633021d75bd36863f29")
]
},
{
"_id" : "5383577a994be8b9a9e3f01e",
"total" : 22,
"ordersId" : [
ObjectId("537b5ea4c61b1d1743f4341f"),
ObjectId("537b4633021d75bd36863f29")
]
}
],
"ok" : 1
}
Now i need to get from orders collection the documents with id from ordersId, and from other collection the documents with _id from the result _id field.
I try with this:
var collection = db.collection('orders');
var result = [];
collection.aggregate([
{
$match: {
'works.TechnicianId': {
$in: ids
},
'works.Date': {
$gte: new Date(startDate),
$lt: new Date(endDate)
}
}
},
{
$unwind: "$works"
},
{
$group: {
_id: "$works.TechnicianId",
total: {
$sum: '$works.price'
},
orderId: {
$push: "$_id"
}
}
}
],
function (e, docs) {
if (e) {
error(e);
}
var usersCollection = db.collection('users');
_.each(docs, function (doc) {
usersCollection.findOne({_id: new ObjectID(doc._id)}, function (e, doc) {
doc.tech = doc;
});
doc.orders = [];
_.each(doc.orderId, function (queryOrder) {
collection.findOne({_id: new ObjectID(queryOrder._id)}, function (e, order) {
doc.orders.push(order);
});
});
success(docs);
});
});
But the success its called before all the _.eachs are finished..Any help, or idea?
Edit:
I try with Q promises, this is my code:
var usersCollection = db.collection('users');
var promises = [];
_.each(reports, function (report) {
var promise = usersCollection.findOne({_id: new ObjectID(report._id)}).then(
function (e, orderUserReported) {
if (e) {
error(e);
}
report.tech = orderUserReported;
_.each(orderUserReported.orderId, function (queryOrder) {
collection.findOne({_id: new ObjectID(queryOrder._id)}, function (e, order) {
report.orders.push(order);
});
});
});
promises.push(promise);
});
Q.allSettled(promises).then(success(reports));
and the error:
/Users/colymore/virteu/aa/server/node_modules/mongodb/lib/mongodb/connection/base.js:245
throw message;
^
TypeError: Cannot call method 'then' of undefined
Because of asynchronous execution you have to wait until results are returned. There are several options available:
async library https://github.com/caolan/async
promises https://github.com/kriskowal/q
Async is closer to your current code, you could use async.parallel https://github.com/caolan/async#parallel to wait untill you get data back
Update
Mongoose functions don't return Q promises, so you need to convert mongoose calls to promises by using something like Q.denodeify(User.findOne.bind(models.User))({ _id: userId}).then(...
For your case Q.denodeify(userCollection.findOne.bind(userCollection))({_id: new ObjectID(report._id)}).then(...
Short answer: Use promises. Look at Q.allSettled ( https://github.com/kriskowal/q )
Just run success asynchronously when all subtask are done.
Also using https://github.com/iolo/mongoose-q package may be helpful to not combine mongoose promises with Q ones if you want use mongoose in your mongo.

$pull/$pop on embedded array mongodb

I have a simple collection with the following schema
{
name:"John",
brands:[
{
name:"some",
email:"asdf#some.com"
},
{
name:"blah"
email:"blah#blah.com"
}
]
}
i'm using the following query to remove the embedded object inside my brands array field:
var args = {
'query':{name:"John",brands.email:"asdf#some.com"}
,update:{
'$pull':{
'brands.$.email:"asdf#some.com"
}
}
}
i'm using nodejs driver for mongodb and when i run the above using following:
collectionName.findAndModify(args,function(req,res){
})
I get the following error:
MongoError: Cannot apply $pull/$pullAll modifier to non-array
I guess i'm doing correct but still getting this error. Any help appreciated.
Your $pull is targeting email which isn't an array. If you're trying to remove the matching element, you can do it like this:
var args = {
query: {name: "John"},
update: {
'$pull': {
brands: {email: "asdf#some.com"}
}
}
}
or if you're trying to remove the email field, use $unset instead:
var args = {
query: {name: "John", "brands.email": "asdf#some.com"},
update: {
'$unset': {
'brands.$.email': 1
}
}
}

Resources