Related
I got this user schema
const UserSchema = new Schema({
email: {
type: String,
required: true,
unique: true,
},
groups: [
{
groupName: {
type: String,
required: true,
},
groupMembers: [{ type: Schema.Types.ObjectId, ref: "GroupMember" }],
},
],
});
And I want to delete a group from the ‘groups’ array based on given ‘userId’ and ‘groupId’, my attempt (with express and mongoose):
router.delete(
"/:userId/:groupId",
catchAsync(async (req, res) => {
const { userId, groupId } = req.params;
const updatedUser = await User.findByIdAndUpdate(
userId,
{ $pull: { "groups.$._id": groupId } },
{ new: true }
);
res.send({ updatedUser });
})
);
The response of the request: I get an error: “The positional operator did not find the match needed from the query.”
Edit:
After I delete a group I need to remove all the group members in the groupMembers array.
User collection structure example:
{
"_id" : "111",
"email" : "michael#gmail.com",
"username" : "michael098",
"groups" : [
{
"_id" : "222"
"groupName" : "family",
"groupMembers" : [
{
"_id" : "333"
"name" : "Liam"
},
{
"_id" : "444"
"name" : "Noah"
}
]
},
{
"_id" : "555"
"groupName" : "friends",
"groupMembers" : [
{
"_id" : "666"
"name" : "Oliver"
}
]
}
]
}
Inside every group there is group members and I have a collection for the group members that I ref in the UserSchema : groupMembers: [{ type: Schema.Types.ObjectId, ref: "GroupMember" }]
GroupMember collection structure example:
{
{
"_id" : "333"
"name" : "Liam"
},
{
"_id" : "444"
"name" : "Noah"
},
{
"_id" : "666"
"name" : "Oliver"
}
}
For example when I get the params of userId="111" and groupId="222" I will delete the whole 'family' group and the whole group members in the groupMembers array (Liam and Noah) from the GroupMember collection.
GroupMember collection after deleting the group with _id="222":
{
{
"_id" : "666"
"name" : "Oliver"
}
}
Assuming an actual doc might look like this (using strings instead of ObjectId to keep the example tighter):
{_id:1, groups: [ { type: "ID1", ref: "XX" }, { type: "ID2", ref: "G1" }, { type: \
"ID3", ref: "G2" } ] }
then this update will remove the subdoc in the groups array where _id = 1 and type = ID2:
db.foo.update({_id:1},
{ $pull: { groups: { type: "ID2" } }}
);
Given the following document in my DB:
{
"_id" : ObjectId("5c03b36d13032cc192d33f84"),
"type" : "Feature",
"properties" : {
"name" : "point 1",
"amenity" : "test",
"popupContent" : "test"
},
"geometry" : {
"type" : "Point",
"coordinates" : [
[ -3.68814468383789,
40.5248912033234]
]
}
}
And given the following mongoose query:
function findVertex(req,res){
Vertex2.find({"_id":"5c03b36d13032cc192d33f84"}, function(err,obj) {
if(err){
res.status(500).send(error);
}else{
var geoJson=obj[0];
console.log(geoJson)
res.status(200).send(geoJson);
}
})
}
What I receive is:
{ properties: { name: 'point 1', amenity: 'test', popupContent: 'test' },
_id: 5c03b36d13032cc192d33f84,
type: 'Feature' }
Why geometry is not in the response? If I do the same query in robomongo I have the object with the coordinates.
Base of mongoDb GeoJSON object documnet your point geometry structure is incorrect, coordinates must be an array with 2 element, not an array that contain another array
At the end I have fixed it changing the model schema.
Initially I had this:
_id:String,
type:String,
properties:{
name:String,
amenity:String,
popupContent:String
},
geometry:{
type:String,
coordinates:[]
}
Now I have this:
type:String,
properties:{
name:String,
amenity:String,
popupContent:String
},
geometry:{
type: {type: String, default: 'MultiPoint'},
coordinates: {type: []}
}
And I have the desired object in return:
{"properties":{"name":"Coors Field","amenity":"Baseball Stadium","popupContent":"This is where the Rockies play!"},"geometry":{"type":"MultiPoint","coordinates":[[-3.68814468383789,40.5248912033234],[-3.70814468383789,40.5248912033234],[-3.68230819702148,40.5074040815359]]},"_id":"5c03a3da82822133f0406667","type":"Feature","__v":0}
I'm curious what the best way to structure my mongoose Schema is. I'm making a movie site with the User schema set like this:
let UserSchema = new mongoose.Schema({
username: {
type: String,
index: true
},
password: {
type: String
},
email: {
type: String
},
name: {
type: String
},
watchlist: [mongoose.Schema.Types.Mixed],
seen: {
like : {
type: [mongoose.Schema.Types.Mixed]
},
dislike: {
type: [mongoose.Schema.Types.Mixed]
}
},
});
When a user clicks either a thumbs up or thumbs down icon, an object of
{movieID: movieID, title: movieTitle, poster: moviePoster}
will be sent to the like or dislike array in the seen property. I'm going to use this info in my EJS template to determine if a movie has already been seen and if seen and liked then gray out the dislike button and if seen and disliked gray out the like button. I also want to be able to remove the item from seen if the use clicks a 'clear' button. In order to do that it seems like I'm going to have to loop over both arrays in order to determine if the movie is included in one and then remove it. Seems like there has to be a better way to do it. I was thinking of structuring the 'seen' object using the movieID as a key, like this:
seen: {
'123': {
movieID: '123',
title: 'movieA',
poster: 'movieA-poster.jpg',
like: true
},
'456' : {
movieID: '456',
title: 'movieB',
poster: 'movieB-poster.jpg',
like: false
}
}
But when I try to send the data to seen using .findOne and this function:
const movieObj = {
id: req.body.movieID,
title: req.body.movieTitle,
poster_path: req.body.poster_path,
};
User.findOne({_id: req.user._id}, function(err, user) {
if (err) {
console.log(err);
} else {
user.seen[req.body.movieID] = movieObj;
user.save(function(){});
}
}
I still just get an empty object returned to me. I changed the schema seen object to:
seen: { type: mongoose.Schema.Types.Mixed, default: {} }
and have set {minimize: false}, because I read that mongoose by default doesn't allow empty objects. Appreciate any guidance on what I'm doing wrong or if you have a better way to efficiently structure the schema to allow seen movies to easily be added or removed from the db.
I think mongoose populate will help you here.
Just read about it, you will get a good idea about it.
Here is a good link with an example:
https://hashnode.com/post/how-do-i-successfully-populate-a-mongoose-schema-cj339r6kt004g5mk83ycujilq
Please look this Models and queries that can help you do build your schemas.
1.User Schema
let UserSchema = new mongoose.Schema({
username: {
type: String,
index: true
},
password: {
type: String
},
email: {
type: String
},
name: {
type: String
},
watchlist: [mongoose.Schema.Types.Mixed],
like : [mongoose.Schema.Types.ObjectId], //remove
deslike : [mongoose.Schema.Types.ObjectId], //remove
seen : [{
movieId : { type: : mongoose.Schema.Types.ObjectId, ref: 'Movie' },
isStatus : {type : string} //Enum [like,dislike,'seen']
}]
})
2.Movie Schema
let movieSchema = new mongoose.Schema({
tile: {
type: String
},
description: { type: String }
})
3.Data store in Both Table
/user/
{
"_id" : ObjectId("5b5acaf0589ff6bfb5dd091f"),
"seen" : [
{
"movieId" : ObjectId("5b9e2544953b5f69683059d4"),
"isStatud" : "like"
},
{
"movieId" : ObjectId("5b9e2544953b5f69683059d6"),
"isStatud" : "dislike"
},
{
"movieId" : ObjectId("5b9e256d953b5f69683059ee"),
"isStatud" : "seen"
}
]
"like" : [
ObjectId("5b9e2544953b5f69683059d4"),
ObjectId("5b9e256d953b5f69683059ee")
],
"deslike" : [
ObjectId("5b9e2544953b5f69683059d6")
]
}
/movie/
{
"_id" : ObjectId("5b9e2544953b5f69683059d4"),
"title" : "movie1",
"description" : "desc1"
}
{
"_id" : ObjectId("5b9e2544953b5f69683059d6"),
"title" : "movie2",
"description" : "desc2"
}
{
"_id" : ObjectId("5b9e256d953b5f69683059ee"),
"title" : "movie3",
"description" : "desc3"
}
Query to get users by movie
/This is working fine and tested/.
db.getCollection('user').aggregate([{
$match : { _id : ObjectId("5b5acaf0589ff6bfb5dd091f") }
},
{$lookup : {
from : 'movie',
localField : 'like',
foreignField : '_id',
as : "likes"
}},
{$lookup : {
from : 'movie',
localField : 'deslike',
foreignField : '_id',
as : "deslikes"
}}
])
/Query with group/
db.getCollection('user').aggregate([{
$match : { _id : ObjectId("5b5acaf0589ff6bfb5dd091f") }
},
{$unwind : '$seen'},
{$lookup : {
from : 'movie',
localField : 'seen.movieId',
foreignField : '_id',
as : "seen.movieData"
}},
{$unwind : '$seen.movieData'},
{ $group: { "_id": "$_id",
"name" : { "$first": "$name" }, //use same all other field of user
"seen" : {"$push" : "$seen"}
} ,
}
])
Please check and let me know any help.
I have 3 different schemas in my application:
userSchema, questionSchema, listingSchema
The relationship between the three is as follows:
Each listing has many questions associated with it (same ones for each listing).
Each user can answer many questions in several listings.
Each question is answered by many users in many listings.
I am trying to wrap my head around defining a correct relationship between those schemas (mainly because of problems like "user with _id = 100 answered a question with _id = 5 in a listing with _id = 3 , how do I update all these in the most efficient way?).
So far I have defined:
questionSchema:
var questionSchema = new Schema({
description: String
});
userSchema:
var userSchema = new Schema({
local : {
email : String,
password : String,
name : String
},
ApartmentsAndQuestions: [{
apartmentID: String,
questionID: [String] /* one apartment -> multiple questions */
}]
});
And listingSchema:
var listingSchema = new Schema({
street : String,
buildingNumber : Number,
apartmentNumber : Number,
type : String,
floor : Number,
outOfFloors : Number,
numberOfRooms : Number,
size : Number,
renovated : Boolean,
elevator : Boolean,
airConditioning : Boolean,
balcony : Boolean,
price : Number,
description : String,
flagCount : Number,
pictures : [imageSchema]
owner : [userSchema]
UsersAndQuestions: [{
userID: String,
questionID: [String] /* one user -> multiple questions asked possible */
}]
});
Question: How do I do it well in my NoSQL database? Do my definitions make sense? Is there a better way to describe the relations between those schemas?
Any help will be greatly appreciated!
MongoDB 3.2+ solution
Add mentionned in comments, you can use new $lookup to avoid embedding lot of data. It's like a SQL LEFT JOIN:
Let's add some data, matching yours:
db.questionSchema.insert({ _id: 1, description: "My description 1" });
db.questionSchema.insert({ _id: 2, description: "My description 2" });
db.questionSchema.insert({ _id: 3, description: "My description 3" });
db.questionSchema.insert({ _id: 4, description: "My description 4" });
db.userSchema.insert({ _id: 1, email: "my#email1.com", ApartmentsAndQuestions: [] });
db.userSchema.insert({ _id: 2, email: "my#email2.com", ApartmentsAndQuestions: [] });
db.listingSchema.insert({ _id: "A", UsersAndQuestions: [] })
db.listingSchema.insert({ _id: "B", UsersAndQuestions: [] })
// Add some questions
db.userSchema.update({ _id: 1 }, { $addToSet: { ApartmentsAndQuestions: { apartment_id: 1, question_id: [1] } } })
db.userSchema.update({ _id: 1, "ApartmentsAndQuestions.apartment_id": 1 }, { $addToSet: { "ApartmentsAndQuestions.$.question_id": 3 } })
db.userSchema.update({ _id: 2 }, { $addToSet: { ApartmentsAndQuestions: { apartment_id: 2, question_id: [1,2] } } })
db.userSchema.update({ _id: 2, "ApartmentsAndQuestions.apartment_id": 2 }, { $addToSet: { "ApartmentsAndQuestions.$.question_id": 4 } })
db.listingSchema.update({ _id: "A" }, { $addToSet: { UsersAndQuestions: { user_id: 1, question_id: [1] } } })
With a regular find, here is what you get:
test> db.listingSchema.find()
{
"_id": "B",
"UsersAndQuestions": [ ]
}
{
"_id": "A",
"UsersAndQuestions": [
{
"user_id": 1,
"question_id": [
1
]
}
]
}
Then, let's $lookup:
db.listingSchema.aggregate([
{
$unwind: "$UsersAndQuestions"
}
,{
$lookup:
{
from: "userSchema",
localField: "UsersAndQuestions.user_id",
foreignField: "_id",
as: "fetched_user"
}
}
,{
$unwind: "$UsersAndQuestions.question_id"
}
,{
$lookup:
{
from: "questionSchema",
localField: "UsersAndQuestions.question_id",
foreignField: "_id",
as: "fetched_question"
}
}
])
You get:
{
"waitedMS": NumberLong("0"),
"result": [
{
"_id": "A",
"UsersAndQuestions": {
"user_id": 1,
"question_id": 1
},
"fetched_user": [
{
"_id": 1,
"email": "my#email1.com",
"ApartmentsAndQuestions": [
{
"apartment_id": 1,
"question_id": [
1,
3
]
}
]
}
],
"fetched_question": [
{
"_id": 1,
"description": "My description 1"
}
]
}
],
"ok": 1
}
Then, you could $unwind ApartmentsAndQuestions.questions_id too and $lookup questions data too. It's up to you.
You will want to define your schemas as follows:
var userSchema = mongoose.Schema({
local : {
email : String,
password : String,
name : String
},
/* every entry in the array is an apartment ID and the questionsIDs (array) of the questions that the user ALREADY answered in that *specific* apartment */
ApartmentsAndQuestions: [{
apartmentID : String,
questionsIDs: [String]
}]
});
And:
var listingSchema = new Schema({
street : String,
buildingNumber : Number,
apartmentNumber : Number,
type : String,
floor : Number,
outOfFloors : Number,
numberOfRooms : Number,
size : Number,
renovated : Boolean,
elevator : Boolean,
airConditioning : Boolean,
balcony : Boolean,
price : Number,
description : String,
flagCount : Number,
ownerID : String,
/* every entry in the array is a userID and the questionsIDs (array) of the questions that the user ALREADY answered in that *specific* apartment */
UsersAndQuestions: [{
userID: String,
questionID: [String]
}],
/* every image has a count of how many times the users answered YES or NO on it */
imagesAndCount: [{
imageID: String,
count: Number
}]
});
Then you can basically do something in the lines of:
var someuser = db.users.find()[2] // get some user
someuser._id >>> which returns some ObjectId("56472a83bd9fa764158d0cb6")
Then: db.users.find({_id: ObjectId("56472a83bd9fa764158d0cb6")}) >>> which will return someuser (with all the fields that are defined in the User schema)
Then: db.listings.insert({"street" : "SomeStreet", "buildingNumber" : 33, "apartmentNumber" : 63, "beds": 3, "owner" : "56472a83bd9fa764158d0cb6"})
And the listing will look like this:
Now the listing looks like this:
{
"_id": {
"$oid": "566c220abcda51a9eef08576"
},
"street": "SomeStreet",
"buildingNumber": 33,
"apartmentNumber": 63,
"beds": 3,
"owner": "56472a83bd9fa764158d0cb6"
}
Schema Definitions
Team.js
var TeamSchema = new Schema({
// Team Name.
name: String,
lead: String,
students :type: [{
block : Number,
status : String,
student : {
type: Schema.ObjectId,
ref: 'Student'
}]
});
Student.js
var StudentSchema = new Schema({
name: String,
rollNo : Number,
class : Number
});
How I can populate "student" to get output, as below:
team
{
"__v": 1,
"_id": "5252875356f64d6d28000001",
"students": [
{
"__v": 1,
"_id": "5252875a56f64d6d28000002",
block : 1,
status : joined,
"student": {
"name": Sumeeth
"rollNo" : 2
"class" : 5
}
},
{
"__v": 1,
"_id": "5252875a56f64d6d28000003",
block : 1,
status : joined,
"student": {
"name": Sabari
"rollNo" : 3
"class" : 4
}
}
],
"lead": "Ratha",
}
This is JS I use to get the document using Mongoose:
Team.findOne({
_id: req.team._id
})
.populate('students')
.select('students')
.exec(function(err, team) {
console.log(team);
var options = {
path: 'students.student',
model: 'Student'
};
Student.populate(team.students,options,function(err, students) {
console.log(students);
if (err) {
console.log(students);
res.send(500, {
message: 'Unable to query the team!'
});
} else {
res.send(200, students);
}
});
});
In my console output I get the following:
{ _id: 53aa434858f760900b3f2246,
students
[ { block : 1
status: 'joined'
_id: 53aa436b58f760900b3f2249 },
{ block : 1
status: 'joined'
_id: 53aa436b58f760900b3f2250 }]
}
And the expected output is:
{ _id: 53aa434858f760900b3f2246,
students
[ { block : 1
status: 'joined'
student :{
"name": Sumeeth
"rollNo" : 2
"class" : 5
}
},
{ block : 1
status: 'joined'
student :{
"name": Sabari
"rollNo" : 3
"class" : 4
}
}
]
}
Some one please help me where I am wrong. How should I make use of .populate, so that , I can get the entire student object and not only its id.
Reference :
Populate nested array in mongoose
I have been facing same issue. I have use this code for my rescue :
Team.findOne({_id: req.team._id})
.populate({ path: "students.student"})
.exec(function(err, team) {
console.log(team);
});
Here is a simplified version of what you want.
Basic data to set up, first the "students":
{
"_id" : ObjectId("53aa90c83ad07196636e175f"),
"name" : "Bill",
"rollNo" : 1,
"class" : 12
},
{
"_id" : ObjectId("53aa90e93ad07196636e1761"),
"name" : "Ted",
"rollNo" : 2,
"class" : 12
}
And then the "teams" collection:
{
"_id" : ObjectId("53aa91b63ad07196636e1762"),
"name" : "team1",
"lead" : "me",
"students" : [
{
"block" : 1,
"status" : "Y",
"student" : ObjectId("53aa90c83ad07196636e175f")
},
{
"block" : 2,
"status" : "N",
"student" : ObjectId("53aa90e93ad07196636e1761")
}
]
}
This is how you do it:
var async = require('async'),
mongoose = require('mongoose');
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/team');
var teamSchema = new Schema({
name: String,
lead: String,
students: [{
block: Number,
status: String,
student: {
type: Schema.ObjectId, ref: 'Student'
}
}]
});
var studentSchema = new Schema({
name: String,
rollNo: Number,
class: Number
});
var Team = mongoose.model( "Team", teamSchema );
var Student = mongoose.model( "Student", studentSchema );
Team.findById("53aa91b63ad07196636e1762")
.select('students')
.exec(function(err, team) {
console.log( team );
async.forEach(team.students, function(student,callback) {
Student.populate(
student,
{ "path": "student" },
function(err,output) {
if (err) throw err;
callback();
}
);
},function(err) {
console.log( JSON.stringify( team, undefined, 4 ) );
});
});
And it gives you the results:
{
"_id": "53aa91b63ad07196636e1762",
"students": [
{
"block": 1,
"status": "Y",
"student": {
"_id": "53aa90c83ad07196636e175f",
"name": "Bill",
"rollNo": 1,
"class": 12
}
},
{
"block": 2,
"status": "N",
"student": {
"_id": "53aa90e93ad07196636e1761",
"name": "Ted",
"rollNo": 2,
"class": 12
}
}
]
}
You really do not need the "async" module, but I am just "in the habit" as it were. It doesn't "block" so therefore I consider it better.
So as you can see, you initial .populate() call does not do anything as it expects to "key" off of an _id value in the foreign collection from an array input which this "strictly speaking" is not so as the "key" is on "student" containing the "foreign key".
I really did cover this in a recent answer here, maybe not exactly specific to your situation. It seems that your search did not turn up the correct "same answer" ( though not exactly ) for you to draw reference from.
You are overthinking it. Let Mongoose do the work for you.
Team.findOne({
_id: req.team._id
})
.populate({path:'students'})
.exec(function(err, team) {
console.log(team);
});
This will return students as documents rather than just the ids.
TL DR
const team = await Team.findById(req.team._id)
.populate("students");
team.students = await Student.populate(team.students, {path: "student"});
Context
Reading from all the answers I went testing everything and just Neil Lun's answer worked for me. The problem is it was on the path to a cb hell. So I cracked my head a little and 'refactored' to an elegant one-liner.
const foundPost = await Post.findById(req.params.id)
.populate("comments")
.populate("author");
foundPost.comments = await User.populate(foundPost.comments, {path: "author"});
My initial problem:
{
title: "Hello World",
description: "lorem",
author: {/* populated */},
comments: [ // populated
{text: "hi", author: {/* not populated */ }}
]
};
How my models basically are:
User = {
author,
password
};
Post = {
title,
description,
author: {}, //ref User
comments: [] // ref Comment
};
Comment = {
text,
author: {} // ref User
};
The output after problem solved:
{
comments: [
{
_id: "5dfe3dada7f3570b60dd977f",
text: "hi",
author: {_id: "5df2f84d4d9fcb228cd1df42", username: "jo", password: "123"}
}
],
_id: "5da3cfff50cf094c68aa2a37",
title: "Hello World",
description: "lorem",
author: {
_id: "5df2f84d4d9fcb228cd1aef6",
username: "la",
password: "abc"
}
};