Reference is working like Embed while Associating - javascript

I was learning Associations in MongoDB. There I learned how to use Embed, but while working on References, I got stuck for few days as its working same as Embed. Instead of showing just the ObjectId in the array, the whole object is being added to the array.
Here's the snippet,
campground schema:
var campgroundSchema = new mongoose.Schema({
name: String,
image: String,
description: String,
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
comment schema:
var commentSchema = mongoose.Schema({
text: String,
author: String
});
Models
var Campground = mongoose.model("Campground", campgroundSchema);
var Comment = mongoose.model("Comment", commentSchema);
Output:
{ "_id" : ObjectId("5a71970ec0379521607b5f47"), "comments" : ***[ { "_id" :
ObjectId("5a71970ec0379521607b5f4a"), "text" : "this place is gr8", "author"
: "Homer", "__v" : 0 } ]***, "name" : "Clould's Rest", "image" :
"https://farm7.staticflickr.com/6014/6015893151_044a2af184.jpg",
"description" : "blah blah", "__v" : 1 }
what I mean to say is instead of showing the whole object inside comments array, the array must show only the reference i.e ObjectId
like this
{ "_id" : ObjectId("5a71970ec0379521607b5f47"), "comments"
: ObjectId("5a71970ec0379521607b5f4a") ], "name" : "Clould's Rest", "image" :
"https://farm7.staticflickr.com/6014/6015893151_044a2af184.jpg",
"description" : "blah blah", "__v" : 1 }
that's how references work right?

Related

How to find data on MongoDB by passing part of array objects

I tried to create an API for filtering the products by sending an array of objects as filters.
this is my Product schema:
const mongoose = require("mongoose");
const { s, rs, rn, rref, ref } = require("../utils/mongo");
let schema = new mongoose.Schema(
{
user: rref("user"),
name: rs,
description: s,
images: [s],
price: rn,
category: ref("category"),
filters: [
{
parent: ref("filter"),
value: s,
name: s,
},
],
subFilter: [
{
parent: s,
value: s,
title: s,
},
],
},
{ timestamps: true }
);
module.exports = mongoose.model("product", schema);
and this one is what I want to send as body to the API
{
category: '62445c3d922d127512867245'
filters: [
{ name: 'filter name 1', value: '62445c3d922d127512861236' },
{ name: 'filter name 2', value: '62445c3d922d127512861458' },
.....
]
}
as you see I want to filter my products based on category Id and an array of filter objects. I tried to write this query but it return an empty array.
this is my query:
filter: async (req, res) => {
try {
const { category, filters } = req.body;
const products = await Product.find({
category,
filters: {
$in: filters,
},
});
res.status(200).json(products);
} catch (err) {
res.status(500).json(err);
}
},
what stored on db
{
"_id" : ObjectId("62643acf19636d7db1804cb3"),
"images" : [
"image-1650735823476۸.jpg"
],
"user" : ObjectId("622606af0f40cb8ea37383dc"),
"name" : "شیر توپی 2 اینچ کلاس 150 پیشگام",
"description" : " برند پیشگام با مدارک و تاییدیه ",
"price" : NumberInt(5000000),
"category" : ObjectId("62445c4d922d127512867246"),
"filters" : [
{
"_id" : ObjectId("62643acf19636d7db1804cb4"),
"parent" : ObjectId("6264307f19636d7db1804b77"),
"value" : "626430bb19636d7db1804b78",
"name" : "Valve Type"
},
{
"_id" : ObjectId("62643acf19636d7db1804cb5"),
"parent" : ObjectId("6264319819636d7db1804b7b"),
"value" : "6264319819636d7db1804b7e",
"name" : "Body Type"
},
{
"_id" : ObjectId("62643acf19636d7db1804cb6"),
"parent" : ObjectId("626431ef19636d7db1804b82"),
"value" : "626431ef19636d7db1804b83",
"name" : "Bore Type"
},
{
"_id" : ObjectId("62643acf19636d7db1804cb7"),
"parent" : ObjectId("6264328519636d7db1804b85"),
"value" : "6264328519636d7db1804b86",
"name" : "Material Type"
},
{
"_id" : ObjectId("62643acf19636d7db1804cb8"),
"parent" : ObjectId("626435de19636d7db1804c10"),
"value" : "626439b619636d7db1804ca7",
"name" : "Trim Material"
},
{
"_id" : ObjectId("62643acf19636d7db1804cb9"),
"parent" : ObjectId("6264367919636d7db1804c17"),
"value" : "6264367919636d7db1804c18",
"name" : "End Conection"
},
{
"_id" : ObjectId("62643acf19636d7db1804cba"),
"parent" : ObjectId("626436a719636d7db1804c1f"),
"value" : "6264378119636d7db1804c28",
"name" : "Size"
},
{
"_id" : ObjectId("62643acf19636d7db1804cbb"),
"parent" : ObjectId("6264389219636d7db1804c6d"),
"value" : "6264389219636d7db1804c6f",
"name" : "Class / Pressure"
}
],
"subFilter" : [
{
"_id" : ObjectId("62643acf19636d7db1804cbc"),
"parent" : "6264328519636d7db1804b85",
"value" : "626433b919636d7db1804b93",
"title" : "Body Material"
}
],
"createdAt" : ISODate("2022-04-23T17:43:43.421+0000"),
"updatedAt" : ISODate("2022-04-23T17:53:29.016+0000"),
"__v" : NumberInt(0)
}
Consider this shrunk down set of inputs that capture the essence of the question. The comments "give away" what we are going to try to find and why. We only show one value for category because matching on that is trivial and not the interesting part of the query.
[
{
"category" : ObjectId("62445c4d922d127512867246"),
"filters" : [
// Matching Valve/value; include this doc
{"name" : "Valve", "value" : "626430bb19636d7db1804b78"},
// ALSO match Body/value; include this doc (but needs only 1 match)
{"name" : "Body", "value" : "6264319819636d7db1804b7e"}
]
}
,{
"category" : ObjectId("62445c4d922d127512867246"),
"filters" : [
// Not target value for Valve name (..79 instead of ...78):
{"name" : "Valve", "value" : "626430bb19636d7db1804b79"},
// ...but correct value for Body, so include this doc
{"name" : "Body", "value" : "6264319819636d7db1804b7e"}
]
}
,{
"category" : ObjectId("62445c4d922d127512867246"),
// No matching Valve or Body so this whole doc is ignored.
"filters" : [
{"name" : "Valve", "value" : "626430bb19636d7db1804b79"},
{"name" : "Body", "value" : "6264319819636d7db1804b7f"}
]
}
,{
"category" : ObjectId("62445c4d922d127512867246"),
// Not even name matches so ignore this too:
"filters" : [
{"name" : "Pipe", "value" : "6264319819636d7db1804eee"}
]
}
]
Assume also we set up inputs coming from the API like this, in their native form i.e. strings NOT ObjectId:
var targ_cat = '62445c4d922d127512867246';
var any_one_of = [
{ name: 'Valve', value: '626430bb19636d7db1804b78' },
{ name: 'Body', value: '6264319819636d7db1804b7e'}
];
We will use $filter as our main function but to do so, we must convert the incoming material into a form required by $filter.
// Convert inbound array of any_one_of into a something designed to work
// in the $filter function by comparing each name/value entry in the
// filters field to the item presented in $$this, meaning take:
// { name: 'Valve', value: '626430bb19636d7db1804b78' },
// and turn it into:
// {$and: [ {$eq:['Valve','$$this.name']}, {$eq:['62643...','$$this.value']} ] }
// Since any one of the entries is considered a hit, we package it all
// into an $or wrapper, not $and.
var or_list = [];
any_one_of.forEach(function(f) {
or_list.push( {$and: [
{$eq:[f['name'], '$$this.name']},
{$eq:[f['value'], '$$this.value']}
]});
});
var or_expr = {$or: or_list};
Now we are ready to query mongoDB:
db.foo.aggregate([
// Get this out of the way quickly; note we must make a new ObjectId!
{$match: {'category': new ObjectId(targ_cat)}}
// The interesting part of the query:
,{$addFields: {filters: {$filter: {input: '$filters', cond: or_expr}}}}
// Only keep those items where $filter found at least one of the
// targets:
,{$match: {$expr: {$gt:[{$size: '$filters'},0]} }}
]);

Get nest object by ID in MongoDB document

I want to retrieve a comment in a post to edit. I'm not sure how to approach this.
Here's what my Post document looks like:
{
"title" : "First Node.js App",
"body" : "testing 123",
"status" : "public",
"user" : "John Doe",
"date" : ISODate("2017-12-21T18:30:09.779Z"),
"comments" : [
{
"commentBody" : "This is awesome! ",
"commentUser" : ObjectId("5a3bfd5a9e65351f9c18ba18"),
"_id" : ObjectId("5a3c02379e65351f9c18ba1a"),
"commentDate" : ISODate("2017-12-21T18:49:27.620Z")
},
{
"commentBody" : "This is second comment.",
"commentUser" : ObjectId("5a3bfd5a9e65351f9c18gt19"),
"_id" : ObjectId("5a3c02379e65351f9c18ba1b"),
"commentDate" : ISODate("2017-12-21T18:49:27.620Z")
}
],
"allowComments" : true
}
How do I retrieve comment id ObjectId("5a3c02379e65351f9c18ba1a")?
I've tried the following but had no luck:
const post = await Post.findById(req.params.id);
const comment = post.comments.find({"_id": req.params.commentid});
You need to pass a callback to Array#find:
const comment = post.comments.find((el) => el._id === req.params.commentid);

Apply an expression to each array element in document

I've got a sample document that I'm trying to project within a MongoDB aggregate pipeline. I'm testing with a single document that looks roughly like this:
{
"_id" : "",
"title" : "Questions",
"sortIndex" : 0,
"topics" : [
{
"_id" : "",
"title" : "Creating a Question",
"sortIndex" : 1,
"thumbnail" : "CreatingAQuestion.jpg",
"seenBy" : [ "user101", "user202" ],
"pages" : [
{
"visual" : "SelectPlanets.gif",
"text" : "Some Markdown"
}
]
},
{
"_id" : "",
"title" : "Deleting a Question",
"sortIndex" : 0,
"thumbnail" : "DeletingAQuestion.jpg",
"seenBy" : [ "user101" ],
"pages" : [
{
"visual" : "SelectCard.gif",
"text" : "Some Markdown"
}
]
}
]
}
The output I'm trying to obtain is something along these lines:
{
"_id" : "",
"title" : "Questions",
"topics" : [
{
"title" : "Creating a Question",
"thumbnail" : "CreatingAQuestion.jpg",
"seen" : true
},
{
"title" : "Deleting a Question",
"thumbnail" : "DeletingAQuestion.jpg",
"seen" : false
}
]
}
Specifically the bit I'm struggling with is the seen flag.
I've read the docs which state:
When projecting or adding/resetting a field within an embedded document...
... Or you can nest the fields:
contact: { address: { country: <1 or 0 or expression> } }
I wish to use an expression and I took note of the following:
When nesting the fields, you cannot use dot notation inside the embedded document to specify the field, e.g. contact: { "address.country": <1 or 0 or expression> } is invalid.
So I'm trying to work out how to "reference" a subdocument field within an expression. That quote suggests I can't use dot notation but when I can't seem to get it working with nested notation either. Here's what I've got so far:
db
.getCollection('chapters')
.aggregate([
{
$project: {
title: 1,
topics: {
title: 1,
thumbnail: 1,
publishedAt: 1,
test: "$seenBy",
seen: { $in: ["user202", "$seenBy"] },
}
}
}
])
So I've hard coded user202 into my query for now, and expected to see true and false for the 2 documents. I've also put in a test field to map out the seenBy field from the sub-document. What this produces is:
{
"_id" : "",
"title" : "Questions",
"topics" : [
{
"title" : "Creating a Question",
"thumbnail" : "CreatingAQuestion.jpg",
"test" : [
"user101",
"user202"
],
"seen" : true
},
{
"title" : "Deleting a Question",
"thumbnail" : "DeletingAQuestion.jpg",
"test" : [
"user101",
"user202"
],
"seen" : true
}
]
}
So obviously my "$seenBy" isn't accessing the correct topic because the test field contains the data from the 1st document.
So ultimately my question is, how can I access the seenBy field within a subdocument, referring to the current subdocument so I can create an expression?
Note: I have got this working with multiple $project and an $unwind but wanted to try compress/clean it up a bit.
You really need to use $map here. Simply notating the array in projection ( which is a bit of a boon since MongoDB 3.2 ) does not really cut it when you need a localized value for the current element. That is what you need and it's what $map provides:
db.getCollection('chapters').aggregate([
{ $project: {
title: 1,
topics: {
$map: {
input: "$topics",
as: "t",
in: {
title: "$$t.title",
thumbnail: "$$t.thumbnail",
publishedAt: "$$t.publishedAt",
test: "$$t.seenBy",
seen: { $in: ["user202", "$$t.seenBy"] },
}
}
}}
])
So for each element the current value of "seenBy" as a property is being tested by the expression. Without the $map that is not possible, and you can only really notate the "whole" array. Which is really not what you want to test here.

What is causing "The dollar ($) prefixed field '$conditionalHandlers' in 'collaborators..$conditionalHandlers' is not valid for storage."

I am writing a Node/Express/Mongoose (latest versions) application which has "Projects" with a list of "collaborators" which are IDS of "Users". Until now, I've been storing the list of foreign keys as hex strings. This is now making it difficult to perform some slightly more complex aggregation, so I have decided to store them as ObjectId type instead, which makes the joins simpler.
In the function which creates the array, the push(userId) version works fine, adding collaborators to the array. However pushing an ObjectId into the array, or assigning an array containing an ObjectId fails with
"The dollar ($) prefixed field '$conditionalHandlers' in
'collaborators..$conditionalHandlers' is not valid for storage."
function addCollaborator(projectId, userId, fn){
projectModel.findById(projectId, (err, project)=>{
if(err) return fn(err);
project.collaborators.push( new Schema.Types.ObjectId(userId)); // errors
// project.collaborators = [ new Schema.Types.ObjectId(userId) ]; // errors
// project.collaborators.push( userId); // works
project.save((err)=>{
logService.error('Error adding collaborator to project: '+err.toString());
});
fn(null);
});
}
Project model:
const ProjectSchema = new mongoose.Schema({
name: String,
create_date: Date,
administrators: Array, // list of user._id
collaborators: Array, // list of user._id ObjectIds
});
With the text IDs, I get projects looking like:
{ "_id" : ObjectId("594e2222a26ca3505c18c674"),
"name" : "Pips 2nd Project", "create_date" : ISODate("2017-06-24T08:26:10.498Z"),
"collaborators" : [ "5936a3576d6c5a3ef4ee0936" ],
"administrators" : [ "594dbba8186f1a2f5ad7539c" ], "__v" : 1 }
When it breaks, I log the error, and am left with an empty array:
{ "_id" : ObjectId("594e278b6a68a2815b043bd1"),
"name" : "Pips third Project", "create_date" : ISODate("2017-06-24T08:49:15.091Z"),
"collaborators" : [ ],
"administrators" : [ "594dbba8186f1a2f5ad7539c" ], "__v" : 0 }
What I want to achieve is:
{ "_id" : ObjectId("594e2222a26ca3505c18c674"),
"name" : "Pips 2nd Project", "create_date" : ISODate("2017-06-24T08:26:10.498Z"),
"collaborators" : [ Object("5936a3576d6c5a3ef4ee0936") ],
"administrators" : [ "594dbba8186f1a2f5ad7539c" ], "__v" : 1 }
I've seen a few other SO's or github issues, but none seem to explain the problem. This one has the same problem, but "solved" it by using strings - which is the opposite of my issue.
After reading some other posts (e.g.), I realised I was using the wrong method to create an ObjectId from a hex string.
new Schema.Types.ObjectId(userId) // not right!
Should be:
mongoose.Types.ObjectId(userId)
So this works as expected now:
project.collaborators.push( mongoose.Types.ObjectId(userId));
and produces:
{ "_id" : ObjectId("594e278b6a68a2815b043bd1"),
"name" : "Pips third Project", "create_date" : ISODate("2017-06-24T08:49:15.091Z"),
"collaborators" : [ ObjectId("5936a3576d6c5a3ef4ee0936") ],
"administrators" : [ "594dbba8186f1a2f5ad7539c" ], "__v" : 1 }

Finding a particular object in an array mongoDB

I have a collection that is composed of some strings, objects, and one array. Within that array are several objects. I am attempting to remove all of the orders with the object id of ObjectId("587ec66e5ed5cb0061092dbe"). See below for the schema and related data. I have tried everything under the sun.
var campgroundSchema = new mongoose.Schema({
name: String,
image: String,
description: String,
price: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
orders: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Order"
}
]
});
Below is sample data.
{
"_id" : ObjectId("587ec65e5ed5cb0061092dbd"),
"name" : "is Forrest Cool?",
"price" : "",
"image" : "https://dsafd.com",
"description" : "",
"orders" :
[
ObjectId("587ec66e5ed5cb0061092dbe"),
ObjectId("587ec6bc5ed5cb0061092dc0"),
ObjectId("587ec6c05ed5cb0061092dc2"),
ObjectId("587ec7178f628931610636dc"),
ObjectId("587ec71e8f628931610636de")
],
"author" : { "id" : ObjectId("587ec6145ed5cb0061092dbc"),
"username" : "forrest" },
"__v" : 18
}
Thank you so much.
To find the specific element with particular ObjectId inside orders array, You can use $in.
Try this:
//orderObjectId is the object id you want to find inside orders.
Campground.find({orders : {$in : [orderObjectId]}},{'orders.$':1},function(err,result){
...
});
'orders.$':1 will return only that element from orders array
To delete that particular order,
//_id is optional, you can query using orderID also.
Campground.update({
_id :someObjectId, orders : {$in : [orderObjectId]}
},{
$pull : {order : orderObjectId}
},function(err,result){
...
});
Update:
Accroding to your comment, it seems you are having facing problem using this.
Try this:
//if you are receiving orderId as string, convert string to ObjectId
var orderId = mongoose.Schema.Types.ObjectId("587d78b8e898d1e732b3888a");
Campground.find({"orders": {$in : [orderId]}},function(err, result){
if(err){
console.log(err);
} else {
console.log("FOUND RESULT: " + result);
}
});
Hope this helps.

Categories