I'll be grateful for help with Mongoose. I have 3 tables: (Users)Table of users, (Animals)table of animals and table AnimalComments. So (AnimalComments)table reference users and animals.
const schemaComment = new mongoose.Schema({
userRef: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
animalRef: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Animal'
},
content: {
type: String
}
});
const schemaAnimal = new mongoose.Schema({
name: {
type: String
},
isCommentedByMe: {
type : Boolean,
default: false
},
commentCount: {
type : Number,
default: 0
}
});
What I want: I have animals. Users can add comment to animal. When user comment animal his comment is added to table AnimalComments where is stored userRef (userId), animalRef (animalId) and user comment text. Then in request response I want return all animals from table Animals but I need update property commentCount and isCommentedByMe based on value from table AnimalComments.
Response from table Animals:
{
"animals": [
{
"isCommentedByMe": false,
"commentCount": 0,
"name": "Jessica",
"userRef": {
"id": "5dc9bdf3dd5cae00177e184d"
},
"id": "5dcedd48368e9800176f2ef3"
}
]
}
Response from table Users:
{
"users": [
{
"name": "Jony Cash",
"id": "5dc9bdf3dd5cae00177e184d"
}
]
}
Response from table AnimalComments:
{
"comments": [
{
"userRef": "5dc9bdf3dd5cae00177e184d",
"animalRef": "5dcedd48368e9800176f2ef3",
"content": "Sample text"
}
]
}
I want result for exmaple:
{
"animals": [
{
"isCommentedByMe": true,
"commentCount": 4,
"name": "Jessica",
"userRef": {
"id": "5dc9bdf3dd5cae00177e184d"
},
"id": "5dcedd48368e9800176f2ef3"
}
]
}
You don't need to keep isCommentedByMe and commentCount fields in animal schema.
And you need to be able access comments from your animals. But in animal schema there is no field to make that connection. So we need to use virtual population.
So your animal schema must be like this:
const schemaAnimal = new mongoose.Schema(
{
name: {
type: String
}
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
);
// Virtual populate
schemaAnimal.virtual("comments", {
ref: "Comment", //must be changed to the name you used for Comment model.
foreignField: "animalRef",
localField: "_id"
});
Now, we can use the following code to populate the comments.
router.get("/animals", async (req, res) => {
const animals = await Animal.find({}).populate("comments");
res.send(animals);
});
This will give you a result like this:
[
{
"_id": "5dd66c73069f88614c12b394",
"name": "Animal 1",
"__v": 0,
"comments": [
{
"_id": "5dd66cfd069f88614c12b39a",
"userRef": "5dd66b54c5195127ec5a1b82",
"animalRef": "5dd66c73069f88614c12b394",
"content": "User 1 - Animal 1",
"__v": 0
},
{
"_id": "5dd66d30069f88614c12b39d",
"userRef": "5dd66b71c5195127ec5a1b83",
"animalRef": "5dd66c73069f88614c12b394",
"content": "User 2 - Animal 1",
"__v": 0
}
],
"id": "5dd66c73069f88614c12b394"
},
{
"_id": "5dd66c7d069f88614c12b395",
"name": "Animal 2",
"__v": 0,
"comments": [
{
"_id": "5dd66d09069f88614c12b39b",
"userRef": "5dd66b54c5195127ec5a1b82",
"animalRef": "5dd66c7d069f88614c12b395",
"content": "User 1 - Animal 2",
"__v": 0
}
],
"id": "5dd66c7d069f88614c12b395"
},
{
"_id": "5dd66c88069f88614c12b396",
"name": "Animal 3",
"__v": 0,
"comments": [
{
"_id": "5dd66d46069f88614c12b39e",
"userRef": "5dd66b71c5195127ec5a1b83",
"animalRef": "5dd66c88069f88614c12b396",
"content": "User 2 - Animal 3",
"__v": 0
}
],
"id": "5dd66c88069f88614c12b396"
}
]
To convert this result to what you wanted, we can use map like this:
Please note that, you need to set loggedInUserId variable to logged in user's id.
router.get("/animals", async (req, res) => {
const loggedInUserId = "5dd66b54c5195127ec5a1b82";
const animals = await Animal.find({}).populate("comments");
const result = animals.map(animal => {
return {
id: animal._id,
name: animal.name,
isCommentedByMe:
animal.comments.filter(c => c.userRef.toString() === loggedInUserId)
.length > 0,
commentCount: animal.comments.length
};
});
res.send(result);
});
The result will be like this:
[
{
"id": "5dd66c73069f88614c12b394",
"name": "Animal 1",
"isCommentedByMe": true,
"commentCount": 2
},
{
"id": "5dd66c7d069f88614c12b395",
"name": "Animal 2",
"isCommentedByMe": true,
"commentCount": 1
},
{
"id": "5dd66c88069f88614c12b396",
"name": "Animal 3",
"isCommentedByMe": false,
"commentCount": 1
}
]
And the answer for the question in the comments is: (how to reference User)
const animals = await Animal.find({}).populate({
path: "comments",
model: Comment,
populate: [
{
path: "userRef",
model: User
}
]
});
This will give you the userRef like this:
[
{
"_id": "5dd66c73069f88614c12b394",
"name": "Animal 1",
"__v": 0,
"comments": [
{
"_id": "5dd66cfd069f88614c12b39a",
"userRef": {
"_id": "5dd66b54c5195127ec5a1b82",
"name": "User 1",
"__v": 0
},
"animalRef": "5dd66c73069f88614c12b394",
"content": "User 1 - Animal 1",
"__v": 0
},
{
"_id": "5dd66d30069f88614c12b39d",
"userRef": {
"_id": "5dd66b71c5195127ec5a1b83",
"name": "User 2",
"__v": 0
},
"animalRef": "5dd66c73069f88614c12b394",
"content": "User 2 - Animal 1",
"__v": 0
}
],
"id": "5dd66c73069f88614c12b394"
},
{
"_id": "5dd66c7d069f88614c12b395",
"name": "Animal 2",
"__v": 0,
"comments": [
{
"_id": "5dd66d09069f88614c12b39b",
"userRef": {
"_id": "5dd66b54c5195127ec5a1b82",
"name": "User 1",
"__v": 0
},
"animalRef": "5dd66c7d069f88614c12b395",
"content": "User 1 - Animal 2",
"__v": 0
}
],
"id": "5dd66c7d069f88614c12b395"
},
{
"_id": "5dd66c88069f88614c12b396",
"name": "Animal 3",
"__v": 0,
"comments": [
{
"_id": "5dd66d46069f88614c12b39e",
"userRef": {
"_id": "5dd66b71c5195127ec5a1b83",
"name": "User 2",
"__v": 0
},
"animalRef": "5dd66c88069f88614c12b396",
"content": "User 2 - Animal 3",
"__v": 0
}
],
"id": "5dd66c88069f88614c12b396"
}
]
Related
I'm trying to fetch documents which contain whatever I pass in url for filter which must present in title field or in tasks array of those documents:
example: localhost:4000/search?filter=Morning. here I'm looking for documents which contain Morning in their title or in their tasks.
But when I'm hitting the Api it is returning me all the documents.
{
"success": true,
"message": "Successfully fetched todos",
"filteredArray": [
{
"_id": "6386e34e2c65763cf25008f3",
"toDoTitle": {
"title": "Morning routines",
"_id": "6386e34e2c65763cf25008f4",
"createdAt": "2022-11-30T04:59:58.940Z",
"updatedAt": "2022-11-30T04:59:58.940Z"
},
"toDoTasks": {
"tasks": [
"code",
"sleep",
"code",
"eat",
"code"
],
"_id": "6386e34e2c65763cf25008f5",
"createdAt": "2022-11-30T04:59:58.941Z",
"updatedAt": "2022-11-30T04:59:58.941Z"
},
"createdAt": "2022-11-30T04:59:58.941Z",
"updatedAt": "2022-11-30T04:59:58.941Z",
"__v": 0
},
{
"_id": "6386e3552c65763cf25008f7",
"toDoTitle": {
"title": "Morning",
"_id": "6386e3552c65763cf25008f8",
"createdAt": "2022-11-30T05:00:05.813Z",
"updatedAt": "2022-11-30T05:00:05.813Z"
},
"toDoTasks": {
"tasks": [
"code",
"sleep",
"code",
"eat",
"code"
],
"_id": "6386e3552c65763cf25008f9",
"createdAt": "2022-11-30T05:00:05.813Z",
"updatedAt": "2022-11-30T05:00:05.813Z"
},
"createdAt": "2022-11-30T05:00:05.813Z",
"updatedAt": "2022-11-30T05:00:05.813Z",
"__v": 0
},
{
"_id": "6386e35f2c65763cf25008fb",
"toDoTitle": {
"title": "evening",
"_id": "6386e35f2c65763cf25008fc",
"createdAt": "2022-11-30T05:00:15.809Z",
"updatedAt": "2022-11-30T05:00:15.809Z"
},
"toDoTasks": {
"tasks": [
"code",
"sleep",
"code",
"eat",
"code"
],
"_id": "6386e35f2c65763cf25008fd",
"createdAt": "2022-11-30T05:00:15.809Z",
"updatedAt": "2022-11-30T05:00:15.809Z"
},
"createdAt": "2022-11-30T05:00:15.810Z",
"updatedAt": "2022-11-30T05:00:15.810Z",
"__v": 0
},
{
"_id": "6386e3672c65763cf25008ff",
"toDoTitle": {
"title": "evening",
"_id": "6386e3672c65763cf2500900",
"createdAt": "2022-11-30T05:00:23.977Z",
"updatedAt": "2022-11-30T05:00:23.977Z"
},
"toDoTasks": {
"tasks": [
"code"
],
"_id": "6386e3672c65763cf2500901",
"createdAt": "2022-11-30T05:00:23.977Z",
"updatedAt": "2022-11-30T05:00:23.977Z"
},
"createdAt": "2022-11-30T05:00:23.977Z",
"updatedAt": "2022-11-30T05:00:23.977Z",
"__v": 0
},
{
"_id": "6386e3712c65763cf2500903",
"toDoTitle": {
"title": "night",
"_id": "6386e3712c65763cf2500904",
"createdAt": "2022-11-30T05:00:33.286Z",
"updatedAt": "2022-11-30T05:00:33.286Z"
},
"toDoTasks": {
"tasks": [
"code"
],
"_id": "6386e3712c65763cf2500905",
"createdAt": "2022-11-30T05:00:33.286Z",
"updatedAt": "2022-11-30T05:00:33.286Z"
},
"createdAt": "2022-11-30T05:00:33.287Z",
"updatedAt": "2022-11-30T05:00:33.287Z",
"__v": 0
}
]
}
This is my model
const mongoose = require("mongoose");
const schema = mongoose.Schema;
const title = new schema(
{
title:{
type: String,
trim: true
}
},
{
timestamps: true
}
);
const task = new schema(
{
tasks:[{ type: String,trim: true}]
},
{
timestamps: true
}
)
const toDoSchema = new schema({
toDoTitle : title,
toDoTasks: task
},
{
timestamps: true
});
const toDoModel = mongoose.model("toDo", toDoSchema);
module.exports = toDoModel;
this is my controller for filtering:
//importing model
const toDoModel = require('../models/ToDoModel');
//importing utils function
const toDosPerPage = require("../utlis/ToDosPerPage")
const searchController = async (req,res)=>{
try{
const {filter} = req.query
if(!filter) throw new Error("Please provide Something in filter to search");
const filteredArray = await toDoModel.find({
$or:[{title:new RegExp(filter,'i')},{tasks: new RegExp(filter,'i')}]
});
res.status(200).json({
success: true,
message: "Successfully fetched todos",
filteredArray
});
}
catch(err){
res.status(401).json({
success: false,
message: "Some error occuried",
error: err.message
})
console.log(err.message);
}
}
module.exports = searchController;
I want it to return only those documents which contain morning in their title or in their tasks fields.
to find in embeded document use dot .
const filteredArray = await toDoModel.find({
$or:[
{'toDoTitle.title':new RegExp(filter,'i')},
{'toDoTasks.tasks':new RegExp(filter,'i')}
]
});
When Implementing a search filter on a document withh populated documents and array of object, How can I enable the search filter to checkout the populated document or array for the searched word?
What I have only checks through the surface fields
async findAllFeedbacks(user: any, payload: FilterDto) {
if (payload.fields) {
const query = await this.distributionChannelsDocumentModel
.find({
[payload.fields]: {
$regex: `${payload.searchFilter}`,
$options: 'i'
},
})
.clone()
.where({ business: user.business })
.populate({
path: 'feedbackId',
// match: { 'feedbackId[payload.fields]': { $regex: `${payload.searchFilter}` } }
})
return { query, total: query.length }
} else {
const query = await this.distributionChannelsDocumentModel
.find()
.clone()
.where({ business: user.business })
.populate('feedbackId')
return { query, total: query.length }
}
}
Here is a response sample:
{
"query": [
{
"_id": "62a8645641b312b3a04a7233",
"feedbackId": {
"_id": "62a8642b41b312b3a04a722a",
"title": "Monthly Feedback test",
"questions": [
{
"title": "Rate Our Services test",
"subtitle": "Customer retention is our priority",
"type": "SMILEY"
}
],
"thankYouMessage": "Thanks for dropping a feedback",
"allowComment": true,
"allowEmail": true,
"allowFullName": false,
"allowAttachment": false,
"status": "Active",
"business": "62a83f5ab4b33de7307e9cb8",
"user": "62a83f59b4b33de7307e9cb6",
"createdAt": "2022-06-14T10:34:19.709Z",
"updatedAt": "2022-06-14T10:34:19.709Z",
"__v": 0
},
"user": "62a83f59b4b33de7307e9cb6",
"distributionChannelType": [
"email"
],
"meta": [
{
"sender": "string",
"subject": "string",
"positioning": "string",
"timing": "string",
"brandColor": "string"
}
],
"createdAt": "2022-06-14T10:35:02.092Z",
"updatedAt": "2022-06-14T10:35:02.092Z",
"__v": 0
},
{
"_id": "62b1c87510fae7f3a54b13b2",
"feedbackId": {
"_id": "62ac55d0b28630c3a8c9b940",
"title": "Yearly Feedback",
"questions": [
{
"title": "Rate Our Services",
"subtitle": "We love to hear from you! How is your Jumia food experience",
"type": "SMILEY"
}
],
"thankYouMessage": "Thanks for dropping a feedback",
"allowComment": true,
"allowEmail": true,
"allowFullName": false,
"allowAttachment": false,
"status": "Active",
"business": "62ac434542452ccb513f2241",
"user": "62ac434542452ccb513f223f",
"createdAt": "2022-06-17T10:22:08.863Z",
"updatedAt": "2022-06-17T10:22:08.863Z",
"__v": 0
},
"user": "62ac434542452ccb513f223f",
"distributionChannelType": [
"sms"
],
"meta": [
{
"position": "bottom",
"timing": "",
"brandColor": "#444"
}
],
"createdAt": "2022-06-21T13:32:37.895Z",
"updatedAt": "2022-06-21T13:32:37.895Z",
"__v": 0
}
],
"total": 2
}
In the above response, feedbackId is the populated document and questions is the array I would also want to search through when using the search filter
i have course model like
const courseSchema = new mongoose.Schema({
name:{
type: String,
required:[true,'course must have a name.'],
unique:true
},
duration :Number,
description :String,
imageCover :String,
images:Array,
price :Number,
curriculum:
[
{
week:Number,
description:String,
total:Number,
completed:Number,
progress:Number,
links:
[
{
name:String,
link:String,
img:String,
watched:{
type:Boolean,
default:false
}
}
]
}
],
tutors:[
{
type: mongoose.Schema.ObjectId,
ref:'user'
}
]
},
{
toJSON:{virtuals : true},
toObject:{virtuals : true}
});
i want to add new objects to links array in curriculum .client side will patch request with week object id , name and link ,inside update handler i am doing somthing like this.
const cour = await Course.findOneAndUpdate({"caricullum._id":req.params.w},
{$push:{name:req.body.name,link:req.body.link}},{
new : true,
});
w params contain curriculum week _id
{
"_id": {
"$oid": "6138abc106b3ad1d3477b3e2"
},
"images": [],
"tutors": [],
"name": "c/c++",
"duration": 8,
"price": 1299,
"imageCover": "cool_lion.jpg",
"description": "",
"curriculum": [
{
"_id": {
"$oid": "6138abc106b3ad1d3477b3e3"
},
"week": 1,
"description": "introduction to microcontroller and microprocesor",
"links": [
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3e4"
},
"name": "introduction",
"link": "https://www.youtube.com/embed/d0e6ScoS3Sw"
},
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3e5"
},
"name": "difference between mc and mp",
"link": "https://www.youtube.com/embed/dcNk0urQsQM"
},
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3e6"
},
"name": "building with microcontroller vs boards(arduino uno)",
"link": "https://www.youtube.com/embed/IdEcm3GU7TM"
}
]
},
{
"_id": {
"$oid": "6138abc106b3ad1d3477b3e7"
},
"week": 2,
"description": "introduction to arduino uno",
"links": [
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3e8"
},
"name": "introduction to arduino uno",
"link": "https://www.youtube.com/embed/BiCSW6QR6HA"
},
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3e9"
},
"name": "IO PINS",
"link": "https://www.youtube.com/embed/OZGMLOwHYf8"
},
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3ea"
},
"name": "setting up arduno uno for programming",
"link": "https://www.youtube.com/embed/ELUF8m24sZo"
}
]
},
{
"_id": {
"$oid": "6138abc106b3ad1d3477b3eb"
},
"week": 3,
"description": "interfacing with different sensors",
"links": [
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3ec"
},
"name": "LED Blinking(OUTPUT)",
"link": "https://www.youtube.com/embed/dnPPoetX0uw"
},
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3ed"
},
"name": "interfacing with button(INPUT)",
"link": "https://www.youtube.com/embed/58Ynhqmvzoc"
},
{
"watched": false,
"_id": {
"$oid": "6138abc106b3ad1d3477b3ee"
},
"name": "16x2 LCD",
"link": "https://www.youtube.com/embed/Mr9FQKcrGpA"
}
]
}
],
"__v": 0
}
how to query for correct week document with req.params.w and push new document into links array
use arrayFilters
db.collection.update({},
{
$push: {
"curriculum.$[elem].links": {
link: "a",
name: "b",
whatched: "c"
}
}
},
{ new:true,
arrayFilters: [
{
"elem.week": 1
}
]
})
https://mongoplayground.net/p/nLV9UzbJlsc
Here is my mongo schema:
{
"_id": ObjectId("5f8af2fc5f23667adf3bbaf2"),
"score": 2.5,
"questions": [{
"_id": ObjectId("5f8af30d5f23667adf3bbaf5"),
"desc": "some text",
},
{
"_id": ObjectId("5f8af3115f23667adf3bbaf8"),
"desc": "some text",
"options": [{
"_id": ObjectId("5f8af3115f23667adf3bbaf9"),
"name": "some name",
"desc": "description 1"
},
{
"_id": ObjectId("5f8af3115f23667adf3bbafa"),
"name": "some name",
"desc": "description 2"
}
]
}
]
}
I've to update the name and desc of the option having id as 5f8af3115f23667adf3bbaf9 which is in the one of the array elements of the question attribute having id as 5f8af30d5f23667adf3bbaf5 which is again part of the data having id as 5f8af2fc5f23667adf3bbaf2
Tried the following query which is getting executed successfully but not updating the option:
Model.findOneAndUpdate({
_id : ObjectId("5f8af2fc5f23667adf3bbaf2"),
"questions._id": ObjectId("5f8af30d5f23667adf3bbaf5"),
"questions.options._id": ObjectId("5f8af3115f23667adf3bbaf9"),
}, {
$set: {
"questions.$[q].options.$[o].order": data.order,
"questions.$[q].options.$[o].definition": data.definition,
"questions.$[q].options.$[o].type": data.type,
},
},
{
arrayFilters: [{ "q._id": ObjectId(qid) }, { "o._id": ObjectId(oid) }]
})
Is tihs possible to do in a single mongoose findOneAndUpdate method?
Your query is correct, I have just hardcoded object id values in array filter and it is updating the documents. I have updated name and desc as u said. Do try this out. One more thing in mongoose u have to specify the object id as "mongoose.Types.ObjectId".
Therefore in your case it would be like "q._id": mongoose.Types.ObjectId("5f8af3115f23667adf3bbaf8").
And one more thing is that you are using findAndUpdate, try using update only depending on your mongoose version
Here is mongoplayground:
https://mongoplayground.net/p/TP5iCTAC5R_
Query:
db.collection.update({
_id: ObjectId("5f8af2fc5f23667adf3bbaf2"),
"questions._id": ObjectId("5f8af3115f23667adf3bbaf8"),
"questions.options._id": ObjectId("5f8af3115f23667adf3bbaf9")
},
{
$set: {
"questions.$[q].options.$[o].name": "anotherName",
"questions.$[q].options.$[o].desc": "anotherDesc"
}
},
{
arrayFilters: [
{
"q._id": ObjectId("5f8af3115f23667adf3bbaf8")
},
{
"o._id": ObjectId("5f8af3115f23667adf3bbaf9")
}
]
})
Output :
[
{
"_id": ObjectId("5f8af2fc5f23667adf3bbaf2"),
"questions": [
{
"_id": ObjectId("5f8af30d5f23667adf3bbaf5"),
"desc": "some text"
},
{
"_id": ObjectId("5f8af3115f23667adf3bbaf8"),
"desc": "some text",
"options": [
{
"_id": ObjectId("5f8af3115f23667adf3bbaf9"),
"desc": "anotherDesc",
"name": "anotherName"
},
{
"_id": ObjectId("5f8af3115f23667adf3bbafa"),
"desc": "description 2",
"name": "some name"
}
]
}
],
"score": 2.5
}
]
for the following MongoDB results, i'm trying to write a query where the name filed is NOT Demo or Demo 2 the Items.location is equal to hongkong:
{
"_id": ObjectID("573ac4d1ad364cd534a03e15"),
"updatedAt": ISODate("2016-05-17T07:14:25.341Z"),
"createdAt": ISODate("2016-05-17T07:14:25.341Z"),
"name": "Testing",
"Items": {
"date": "18052016",
"location": "hongkong"
},
"__v": 0
},
{
"_id": ObjectID("573ac4d1ad364cd534a03e16"),
"updatedAt": ISODate("2016-05-17T07:14:25.341Z"),
"createdAt": ISODate("2016-05-17T07:14:25.341Z"),
"name": "Demo",
"Items": {
"date": "18052016",
"location": "hongkong demo"
},
"__v": 0
},
{
"_id": ObjectID("573ac4d1ad364cd534a03e16"),
"updatedAt": ISODate("2016-05-17T07:14:25.341Z"),
"createdAt": ISODate("2016-05-17T07:14:25.341Z"),
"name": "Demo 2",
"Items": {
"date": "18052016",
"location": "hongkong demo"
},
"__v": 0
}
My query looks like:
mySchema.statics.testing = function *() {
var output = this.find(
{
name: {
$nin: ["Demo", "Demo 2"]
}
}
,{
Items: {
$elemMatch: { location: "hongkong" }
}
}).exec();
return output;
};
and my schema is below:
var mongoose = require('mongoose');
var mySchema = new mongoose.Schema({
name: String,
Items: Object
}, { timestamps: true });
The query seems to only return the "_id" field for the matched where as i want the all the fields returned, name, Items..etc. What am i missing?
try this code
mySchema.statics.testing = function (fn) {
this.find(
{
name: {
$nin: ["Demo", "Demo 2"]
},
"Items.location": "hongkong"
})
.exec(function (err, docs) {
fn(err, docs)
})
};
fixed. Needed:
var output = this.find(
{
name: {
$nin: ["Demo", "Demo 2"]
},
"Items.location" : "hongkong"
}).exec();
return output;
};