I have a problem finding a solution to populate a filed populated(the number of populate unknowen)
export const FilesSchema = new mongoose.Schema(
{
name: {
type: String,
trim: true,
required: true,
unique: true,
},
children: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Files',
},
],
},
{ timestamps: true },
);
it represent a structure of file that can be a file or a folder.
a folder can contient another folder and another folder with files
the number of population are unknowen . any help with this ?
i found a solution
Say you have a user schema which keeps track of the user's friends.
const userSchema = new Schema({
name: String,
friends: [{ type: ObjectId, ref: 'User' }]
});
Populate lets you get a list of a user's friends, but what if you also wanted a user's friends of friends? Specify the populate option to tell mongoose to populate the friends array of all the user's friends:
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});
https://mongoosejs.com/docs/populate.html#deep-populate
Related
I want to do a big project API where people can login with Google, send the token and when logged in do some actions saved to Mongo DB.
The problem is the tables structure, or schema. So, I want to do a good app scalable.
I have some tables like Users (with users loggin information) and I want user save his tasks, memories, works and more for him self, and when logged in in another device, get this information and modify.
Do I need to do every table for every user or use same table filtered by user?
For example, I have now this model for product:
const ProductoSchema = Schema({
nameOfProduct: {
type: String,
require: [ true, 'Required name' ],
unique: true
},
state: {
type: Boolean,
default: true,
required: true
},
userOwner: {
type: Schema.Types.ObjectId,
ref: 'Usuario',
required: true
},
priece: {
type: Number,
default: 0
},
category: {
type: Schema.Types.ObjectId,
ref: 'Category',
required: true
},
description: {
type: String
},
disponibility: {
type: Boolean,
default: true
},
img: {
type: String
},
});
Is this correct or this is not scalabe and I need to do all tables for every user?
I am developing a server using Expressjs, Mongodb and Mongoose. I need to push an element (a string) into the "tweets" array which is inside an object (a friend) which is in turn inside the "friends" array which is inside a "user" Object which document in the "users" collection. Here is an example of how my documents in the Mongodb collection looks like:
{
"loggedIn": true,
"_id": "5f91ef0ce75d3b1d40539da0",
"username": "username",
"email": "a#h.com",
"password": "$2a$10$9krWS9Kq5024lRTexqaweePrn8aughepqTkaj3oA48x0fJ2ajd79u",
"dateOfBirth": "2002-12-07",
"gender": "male",
"friends": [
{
"tweets": [],
"_id": "5f91effae75d3b1d40539da7",
"username": "Jonas"
},
],
"__v": 0
}
I need to pick the specified username from the "Users" arrary first and then access "friends" array within this user and then pick the right friend object and finally push the tweet on $position: 0 in this array. I I tried to achieve that as shown in this code and I could access the friend object with the given friendUsername
await Users.updateOne(
{ username: req.params.username },
{
$push: {
friends: {
$elemMatch: {
username: req.params.friendUsername,
},
},
},
}
);
And now the question is how to access the "tweets" array inside $elemMatch and push the req.body.tweet at $position: 0 into it?
Here is how I would solve your issue, I first would re-define the way I am defining schemas.
My User schema would look something like the following
User.js
const mongoose = require('mongoose')
const UserSchema = mongoose.Schema({
...
friends: {
type: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
required: true,
default: []
},
tweets: {
type: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Tweet'
}],
required: true,
default: []
},
...
}, {timestamps: true})
module.exports = mongoose.model('User', UserSchema)
User.js
const mongoose = require('mongoose')
const TweetSchema = mongoose.Schema({
...
text: {
type: String,
required: true
},
tweeter: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
likes: {
type: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
required: true,
default: []
},
...
}, {timestamps: true})
module.exports = mongoose.model('Tweet', TweetSchema)
This assumes that every user can have tweets and that a User can be friend with another User
And now if someone tweets something you can do something like
const Tweet = require('./Tweet.js')
const User = require('./User.js')
let tweet = new Tweet({
text: "My first tweet!",
tweeter: "ID Of user who is posting the tweet"
})
tweet.save()
// Now update the user who tweeted
User.findOneAndUpdate()
User.updateOne({ _id: "ID Of user who is posting the tweet" }, { $push: { tweets: tweet._id } })
and now whenever you request a user all of his friends will be referenced and all of their tweets will also be referenced! if you want to see the actual tweets then use something like .populate() here are the docs for .populate() https://mongoosejs.com/docs/populate.html
Keep in mind is really a good practice to only return the actual ids and your frontend takes care of requesting the appropriate objects from their perspective endpoints. And if you wish to reduce network calls then the frontend would cache the data.
If the above doesn't help and you still would like to achieve your goal with your schemas then something like this should work (assuming your schema is called User)
let tweetObj = {}
User.updateOne({_id: 'your userid'}, {$push: {"friends.$.tweets": tweetObj}})
NOTE: I have omitted callbacks as they are irrelevant to the question
Background
Here's part of my User model:
const Group = require("./Group")
...
groups: {
type: [{ type: Schema.ObjectId, ref: Group }],
default: [],
},
And here's my Group model:
module.exports = mongoose.model(
"Group",
new Schema(
{
name: {
type: String,
required: true,
unique: true,
},
/**
* Array of User ObjectIDs that have owner rights on this group
*/
owners: {
type: [{ type: Schema.ObjectId, ref: User }],
default: [],
},
},
{
timestamps: true,
}
)
)
The Code
Here's the code I'm running to try and populate:
const user = await (await User.findOne({ _id: ... })).execPopulate("Group")
console.log(user.groups)
My console.log is outputting an array of object IDs, when I'd like it to output an actual Group document.
Attempted solutions
I've tried changing my ref to be using the string ("Group"), I've tried arranging my query differently, etc. I'm not sure how I'd go about doing this.
Apologies in advance if this is a duplicate, I've done my best to search but can't really find a solution that works for me.
Specifically, what do I need help with?
I'm trying to create a 'link' between a user model and a group model. In my console.log, I expect it to output a Group document; but it outputs an object ID (which is how it's stored raw in the database, meaning that Mongoose isn't transforming it correctly)
When you change execPopulate to populate like:
async function findUserAndPopulate(userId){
const response = await User.findOne({
_id: userId,
}).populate('groups')
console.log("response",response)
}
You got:
{
groups: [
{
owners: [Array],
_id: 5ecc637916a2223f15581ec7,
name: 'Crazy',
createdAt: 2020-05-26T00:31:53.379Z,
updatedAt: 2020-05-26T00:31:53.379Z,
__v: 0
}
],
_id: 5ecc6206820d583b99b6b595,
fullname: 'James R',
createdAt: 2020-05-26T00:25:42.948Z,
updatedAt: 2020-05-26T00:36:12.186Z,
__v: 1
}
So you can access the user.groups
See the doc: https://mongoosejs.com/docs/populate.html
I would like to add a photo into a country in mongoose. But country is an array and photo too. Here is my user schema :
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
birthDate: {
type: Date,
required: true
},
sex: {
type: String,
required: true
},
countries: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Country',
photos: [
{
base64: {
type: String,
required: true
},
title: String,
description: String
}
]
}
],
admin: {
type: Number,
required: true
}
});
Here is what I got as data into mongoDB :
The problem is that I only got the id of countries. And I would like to use another field of the document country. Populate works well when I want to get data, but how to populate and then use the fields to update with mongoDB?
Moreover, I don't know how to update data into nested array, I tried :
User.findOneAndUpdate(
{
"name": "CHARLAT",
"countries": "5d2d847b06f2f94118a36518"
},
{ $push : { "countries.photos" : {
base64: "bla"
} }}
)
As you can see, I use a hand written id for country... I could do a find query before on country but can we use populate here?
And I got this in Postman :
Thank you in advance for your help !
If the type is ObjectId, it can't have a photos field, since it's just an _id. It is a reference to another collection.
Updated answer after comments :
The best thing to do IMO is to create a Photo model which would have the file path and the country's _id. The User model would only store a list of Photos [_id].
UserSchema :
{
.....
photos : [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Photo'
}],
.....
}
PhotoSchema :
{
country : {
type: mongoose.Schema.Types.ObjectId,
ref: 'Country'
},
path : String
}
Then, query your Users this way, by populating the photos, and inside each photo, populating the countries :
UserModel
.find(conditions)
.populate({
path: 'photos',
model: 'Photo'
populate: {
path: 'country',
model: 'Country'
}
})
.lean() // Faster and lighter for read-only, simply returns an object
So you should get a User object like this :
{
.....
name : "John",
photos : [{
country : {
name : "Country 1",
code : "C1" // or whatever field you have in your Country model
},
path: "path/to/photo1.jpg"
},
{
country : {
name : "Country 2",
code : "C2"
},
path: "path/to/photo2.jpg"
}]
.....
}
I'm creating a very basic functionality kanban board.
My board has 4 models so far:
User model
var userSchema = new Schema({
name: {
type: String,
required: true
}
})
module.exports = mongoose.model('User', userSchema)
Board model
var boardSchema = new Schema({
title: {
type: String,
required: true
},
lists: [ listSchema ]
members: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
]
});
module.exports = mongoose.model('Board', boardSchema)
List schema
let listSchema = new Schema({
title: {
type: String,
required: true
},
userCreated: {
type: Schema.Types.ObjectId,
required: true,
ref: 'user'
},
boardId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'board'
},
sort: {
type: Number,
decimal: true,
required: true
}
})
module.exports = mongoose.model('List', listSchema)
Card schema
var cardSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String
},
boardId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'board'
},
listId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'list'
},
members: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
],
sort: {
type: Number,
decimal: true,
required: true
}
})
module.exports = mongoose.model('Card', cardSchema)
What am I looking for?
My front-end is made with Vue.js and sortable.js drag and drop lib.
I want to find the best way to render board with lists (columns) and cards in them.
From what I understand, I should get my board first, by the users id in members array.
Then I have my board which has lists embedded already.
On second api request, I get all the cards by boardId.
My question is - how do I correctly put/render all the cards into each owns lists?
So in the end I want to have something like:
{
title: 'My board',
lists: [
{
title: 'My list',
_id: '35jj3j532jj'
cards: [
{
title: 'my card',
listId: '35jj3j532jj'
}
]
},
{
title: 'My list 2',
_id: '4gfdg5454dfg'
cards: [
{
title: 'my card 22',
listId: '4gfdg5454dfg'
},
{
title: 'my card 22',
listId: '4gfdg5454dfg'
}
]
}
]
members: [
'df76g7gh7gf86889gf989fdg'
]
}
What I've tried?
I've came up with only one thing so far, that is:
Two api calls in mounted hook - one to get the board with lists, second to get all cards.
Then I loop trough lists and loop trough cards and push each card into the list by id?
But this way it seems that my lists would need to have an empty array called cards: [] just for the front-end card-to-list sorting by id, seems somehow wrong.
Is this a good way? Or should I redesign my models and schemas and go with some other way? Any help would be appreciated!
The schema you've defined is pretty good, just one modifications though.
No need to have 'lists' in Board model, since it's already available in lists and also if you keep it in boards, then everytime a new list is added, you'll need to edit the board as well.
Here's how the flow would be.
Initially, when a user signs in, you'll need to show them the list of boards. This should be easy since you'll just do a find query with the user_id on the board collection.
Board.find({members: user_id}) // where user_id is the ID of the user
Now when a user clicks on a particular board, you can get the lists with the board_id, similar to the above query.
List.find({boardId: board_id}) // where board_id is the ID of the board
Similarly, you can get cards with the help of list_id and board_id.
Card.find({boardId: board_id, listId: list_id}) // where board_id is the ID of the board and listId is the Id of the list
Now, let's look at cases wherein you might need data from 2 or more collection at the same time. For example, when a user clicks on board, you not only need the lists in the board but also the cards in that board. In that case, you'll need to write an aggregation as such,
Board.aggregate([
// get boards which match a particular user_id
{
$match: {members: user_id}
},
// get lists that match the board_id
{
$lookup:
{
from: 'list',
localField: '_id',
foreignField: 'boardId',
as: 'lists'
}
}
])
This will return the boards, and in each board, there'll be an array of lists associated with that board. If a particular board doesn't have a list, then it'll have an empty array.
Similarly, if you want to add cards to the list and board, the aggregation query will be a bot more complex, as such,
Board.aggregate([
// get boards which match a particular user_id
{
$match: {members: user_id}
},
// get lists that match the board_id
{
$lookup:
{
from: 'list',
localField: '_id',
foreignField: 'boardId',
as: 'lists'
}
},
// get cards that match the board_id
{
$lookup:
{
from: 'card',
localField: '_id',
foreignField: 'boardId',
as: 'cards'
}
}
])
This will add an array of cards as well to the mix. Similarly, you can get cards of the lists as well.
A bit late to the answer but I think it'll help someone nevertheless. The problem you have could be solved using aggregation framework. While the other answer mentions a pretty good way, it still doesn't have the cards data embedded into it.
MongoDB docs show a way for nested aggregation queries. Nested Lookup
A similar approach could be used for your question.
Board.aggregate([
{
$match: { _id: mongoose.Types.ObjectId(boardId) },
},
{
$lookup: {
from: 'lists',
let: { boardId: '$_id' },
pipeline: [
{ $match: { $expr: { $eq: ['$boardId', '$$boardId'] } } },
{
$lookup: {
from: 'cards',
let: { listId: '$_id' },
pipeline: [{ $match: { $expr: { $eq: ['$listId', '$$listId'] } } }],
as: 'cards',
},
},
],
as: 'lists',
},
},
]);
This will include the cards as an array inside of every list.