I am new at MongoDB. I am trying to create 2 db's :
1 - Dogs
2 - Person
I want a person to have 0, 1 or more dogs, and a partner (who is also a person)
tried to do:
var dogSchema = new mongoose.Schema({
name: String,
age: Number,
breed: String
});
var Dog = mongoose.model("Dog", dogSchema);
var personSchema = new mongoose.Schema({
name: String,
age: Number,
partner: this,
dogs: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Dog"
}
]
});
var Person = mongoose.model("Person", personSchema);
But when I try to create a new person, things getting complicated.
what is the best way to do this?
This code work fine for me:
"use strict"
const mongoose = require('mongoose');
const categorySchema = mongoose.Schema({
name: String,
parent: {type:mongoose.Schema.Types.ObjectId, ref:"Category"},
subcategory: {type:mongoose.Schema.Types.ObjectId, ref:"Category"},
});
const Category = mongoose.model('Category', categorySchema);
async function run() {
await mongoose.connect("mongodb://localhost:27017/mongoose-test");
const category = new Category({name:"Name"});
const subcategory = new Category({name:"Name 2", parent:category});
category.subcategory = subcategory;
await Promise.all([category.save(), subcategory.save()]);
};
run();
Mongo:
> db.categories.find();
{ "_id" : ObjectId("59f4a8f32ebdb940cc2bed3a"), "subcategory" :
ObjectId("59f4a8f32ebdb940cc2bed3b"), "name" : "Name", "__v" : 0 }
{ "_id" : ObjectId("59f4a8f32ebdb940cc2bed3b"), "name" : "Name 2",
"parent" : ObjectId("59f4a8f32ebdb940cc2bed3a"), "__v" : 0 }
Related
I have the following code in a file "test.js", in which I am trying to populate story.fans[0].stories[0]
, but it doesn't work. The rest of the code runs fine, but when it is trying to populate fan[0]'s child object stories, it doesn't seem to work.
Can mongoose popluate 2 level child objects?
const mongoose = require('mongoose');
main().catch(err => console.log(err));
async function main() {
await mongoose.connect('mongodb://localhost:27017/testMongoose');
const Schema = mongoose.Schema;
const personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);
Story.
findOne({ title: 'Casino Royale' }).populate('fans').
exec(function (err, story) {
if (err) return handleError(err);
console.log('the story is',story.title);
console.log('The fans[0] is %s', story.fans[0].name);
story.fans[0].populate('stories');
console.log('the story written by fan is',story.fans[0].stories[0].title);
//option2
story.fans[0].populate('stories').exec(function(err,fan){
console.log('the story written by fan is',fan.stories[0].title);
});
});
}
Here is the error message:
----------------
This is my stories collection:
/* 1 */
{
"_id" : ObjectId("61cfd221256ef6d903523700"),
"author" : ObjectId("61cfd221256ef6d9035236fe"),
"title" : "Casino Royale",
"fans" : [
ObjectId("61cfee8b5059fb3fe37b3c5f")
],
"__v" : 1
}
/* 2 */
{
"_id" : ObjectId("61d09887abeb41f82a7e1678"),
"author" : ObjectId("61cfee8b5059fb3fe37b3c5f"),
"title" : "Story 001",
"fans" : [],
"__v" : 0
}
This is my people collection
/* 1 */
{
"_id" : ObjectId("61cfd221256ef6d9035236fe"),
"name" : "Ian Fleming",
"age" : 50,
"stories" : [],
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("61cfee8b5059fb3fe37b3c5f"),
"name" : "Fan 001",
"age" : 38,
"stories" : [
ObjectId("61d09fbfbd8f3fa20beaa616")
],
"__v" : 14
}
Considering the data provided by you, there is nothing in layer 2 to be populated, since the referenced id in Fan 001 is not matching with any of the stories.
Fans stories:
61d09fbfbd8f3fa20beaa616
==> is not found in
Available stories:
61d09887abeb41f82a7e1678
61cfd221256ef6d903523700
But if you fix your references what you want to be using is called deep Population
You basically populate a field in the object you just populated.
Your code then looks like this:
Story.findOne({ title: 'Casino Royale' })
.populate({ path: 'fans', model: 'Person', populate: { path: 'stories', model: 'Story' } }).
exec(function(err, story) {
if (err) return handleError(err);
console.log(story)
console.log('the story is', story.title);
console.log('The fans[0] is %s', story.fans[0].name);
console.log('the story written by fan is', story.fans[0].stories[0].title);
});
I rebuild the example but fixed the references and the output now looks like expected:
I can't get mongoose to show subdocument when running find() while it displays perfectly well in mongodb shell.
Subdocument should be embedded based on my schema, not objectId referenced, so I shouldn't be running any black magic voodoo to get my data to show up.
const UserSchema = new mongoose.Schema({
username: String;
xp: Number;
//etc.
});
const RoomSchema = new mongoose.Schema({
timestamp: { type: Date, default: Date.now },
status: { type: String, enum: ["pending", "ongoing", "completed"]},
players: {
type: [{
points: { type: Number, default: 0 },
position: String,
user: UserSchema
}],
maxlength:2
}
});
After adding a new room with:
let room = new Room(coreObj);
room.players.push({
points: 0,
position: 'blue',
user: userObj //where userObj is a result of running findById on User model
});
It displays nicely in mongo shell, when running db.rooms.find({}).pretty() I can see that full document has been added. However, when running on mongoose model:
Room.find({}).exec((err,rooms)=>{
console.log(rooms[0].toJSON());
});
I don't see user subdocument, moreover I cannot see user field entirely! What seems to be the problem?
logged json from mongoose model:
{
"status": "pending",
"_id": "5cf5a25c050db208641a2076",
"timestamp": "2019-06-03T22:42:36.946Z",
"players": [
{
"points": 0,
"_id": "5cf5a25c050db208641a2077",
"position": "blue"
}
],
"__v": 0
}
json from mongo shell:
{
"_id" : ObjectId("5cf5a25c050db208641a2076"),
"status" : "pending",
"timestamp" : ISODate("2019-06-03T22:42:36.946Z"),
"players" : [
{
"points" : 0,
"_id" : ObjectId("5cf5a25c050db208641a2077"),
"position" : "blue",
"user" : {
"xp" : 0,
"_id" : ObjectId("5cf2da91a45db837b8061270"),
"username" : "bogdan_zvonko",
"__v" : 0
}
}
],
"__v" : 0
}
Keeping best practice in mind, I think it would be more appropriate to reference the UserSchema in the RoomSchema. Something like:
...
user: {
type: Schema.Types.ObjectId,
ref: 'UserSchema'
}
Then you would store the user._id in that field.
This way, if the user is modified, your RoomSchema is always referencing the correct information. You could then get the user using Mongoose's populate
I'm not entirely sure why you can't see the sub-sub-document, but this code example printed it correctly for me. Example was originally posted in https://mongoosejs.com/docs/subdocs.html but modified slightly to contain sub-sub-document so it looks similar to your code:
var grandChildSchema = new mongoose.Schema({ name: 'string' });
var childSchema = new mongoose.Schema({ name: 'string', grandChild: grandChildSchema });
var parentSchema = new mongoose.Schema({ children: [childSchema] });
var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({
children: [
{ name: 'Matt', grandChild: {name: 'Matt Jr'} },
{ name: 'Sarah', grandChild: {name: 'Sarah Jr'} }
]
})
parent.save(function() {
Parent.find().exec(function(err, res) {
console.log(JSON.stringify(res[0]))
mongoose.connection.close()
})
});
Executing this code resulted in:
{
"_id": "5cf7096408b1f54151ef907c",
"children": [
{
"_id": "5cf7096408b1f54151ef907f",
"name": "Matt",
"grandChild": {
"_id": "5cf7096408b1f54151ef9080",
"name": "Matt Jr"
}
},
{
"_id": "5cf7096408b1f54151ef907d",
"name": "Sarah",
"grandChild": {
"_id": "5cf7096408b1f54151ef907e",
"name": "Sarah Jr"
}
}
],
"__v": 0
}
This was tested using Mongoose 5.5.12.
Note that I was using JSON.stringify() to print the document instead of using Mongoose's toJSON().
I just met a very similar problem, i think i got it.
the whole point is in model which you use:
const RoomSchema = new mongoose.Schema({
...
players: {
type: [{
...
user: UserSchema
...
but then you make
room.players.push({
points: 0,
position: 'blue',
user: userObj //where userObj is a result of running findById on User model
});
so you are missing the "type" subfield, so your doc is not compliant with your RoomSchema and mongoose do not show the parts which does not fit schema.
I've got an Express application which until this morning was returning an array of objects from the database. Now it's returning an empty array. RoboMongo shows me that the data is still there and doing fine. Any ideas?
My model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const plotStatusSchema = new Schema(
{
recordDate: Date,
blockName: String,
growerName: String,
company: String,
variety: String,
planted: Number,
region: String,
yieldInKG: Number,
changeInPcnt: Number,
currentRipeness: String,
nextStage: String,
timeToNextInDays: Number,
status: Number
},
{ bufferCommands: false },
{ collection: 'plotStatuses' }
);
const ModelClass = mongoose.model(
'plotStatus',
plotStatusSchema,
'plotStatuses'
);
module.exports = ModelClass;
My returning controller:
const PlotStatus = require('../models/plotStatus');
const jsonpack = require('jsonpack');
exports.plotStatuses = async (req, res) => {
const plotStatus = await PlotStatus.find({
company: 'req.user.companyCode'
}).lean();
if (!plotStatus) {
throw new Error('Plot Statuses not found');
} else {
res.send(plotStatus);
}
};
A sample of my data:
{
"_id" : ObjectId,
"recordDate" : ISODate,
"blockName" : String,
"blockCode" : String,
"growerName" : String,
"company" : String,
"variety" : String,
"planted" : ISODate,
"region" : String,
"yieldInKG" : Number,
"changeInPcnt" : Number,
"currentRipeness" : String,
"nextStage" :String,
"timeToNextInDays" : Number,
"status" : Number,
"targetYieldInKG" : Number,
"currentStatePercentage" : Number,
"totalLengthOfPhase" : Number,
"nextPhaseStart" : ISODate,
"currentBrix" : Number,
"currentPh" : Number,
"currentTA" : Number,
"plotGeoJSON" : Object,
"historicalData" : Array
}
I know that the Schema no longer matches the shape of the JSON, but can I return all the JSONs that fit the find condition anyway?
You are searching the string "req.user.companyCode" inside the company attribute. Obviously, you don't have that company code in your data. Try it again without the quores:
const plotStatus = await PlotStatus.find({
company: req.user.companyCode
}).lean();
I'm using Express to do a find operation in a Mongo collection and return the result. I know that there is data in the collection, but the result array comes back empty. I suspect that it's an issue with the schema, but can't figure out what. Thoughts?
Route:
const PlotStatus = require('../models/plotStatus');
exports.plotStatuses = async (req, res) => {
const plotStatus = await PlotStatus.find({
company: req.user.companyCode
}).lean();
if (!plotStatus) {
throw new Error('Plot Statuses not found');
} else {
res.send(plotStatus);
}
};
Model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const plotStatusSchema = new Schema(
{
recordDate: Date,
blockName: String,
growerName: String,
company: String,
variety: String,
planted: Number,
region: String,
yieldInKG: Number,
currentRipeness: String,
nextStage: String,
timeToNextInDays: Number,
status: Number
},
{ bufferCommands: false },
{ collection: 'plotStatuses' }
);
const ModelClass = mongoose.model('plotStatus', plotStatusSchema);
module.exports = ModelClass;
Sample plotStatus:
{
"_id" : ObjectId("stringhere"),
"recordDate" : ISODate("2018-01-02T12:50:51.236Z"),
"blockName" : "name",
"growerName" : "grower's name",
"company" : "mycompany",
"variety" : "myvariety",
"planted" : 2010,
"region" : "myregion",
"yieldInKG" : 960,
"changeInPcnt" : -1.6,
"currentRipeness" : "ripeness",
"nextStage" : "nextstage",
"timeToNextInDays" : 42,
"status" : 0
}
Okay, got it.
Typically, Mongoose infers the collection name from the model name, but you can pass it explicitly. Apparently, plotStatus => plotStatuses was a bridge too far. This fixed it:
const ModelClass = mongoose.model(
'plotStatus',
plotStatusSchema,
'plotStatuses'
);
Lemme take time to explain what is happening from start to finish.
Preamble:
A user a follows 10 other people. When user A logs in, an X number of posts from each of the 10 people are pulled into view.
I do not know if it is the right thing to do, and will appreciate a better way of doing it. However, I wanna give it a try, and it ain't working.
Follow Model:
let mongoose = require('mongoose');
let Schema = mongoose.Schema;
let FollowSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
followers: [{
type: Schema.Types.ObjectId,
ref: 'Card'
}],
following: [{
type: Schema.Types.ObjectId,
ref: 'Card'
}]
});
module.exports = mongoose.model('Follow', FollowSchema);
Card Model
let mongoose = require('mongoose');
let Schema = mongoose.Schema;
let CardSchema = new Schema({
title: String,
content: String,
createdById: {
type: Schema.Types.ObjectId,
ref: 'User'
},
createdBy: {
type: String
}
});
module.exports = mongoose.model('Card', CardSchema);
Follow logic
When user A follows user B, do two things:
Push the user_id of B to user A document on field 'following' (A is following B)
Push user_id of A to user B document on field 'followers' (B is followed by A)
router.post('/follow', utils.loginRequired, function(req, res) {
const user_id = req.user._id;
const follow = req.body.follow_id;
let bulk = Follow.collection.initializeUnorderedBulkOp();
bulk.find({ 'user': Types.ObjectId(user_id) }).upsert().updateOne({
$addToSet: {
following: Types.ObjectId(follow)
}
});
bulk.find({ 'user': Types.ObjectId(follow) }).upsert().updateOne({
$addToSet: {
followers: Types.ObjectId(user_id)
}
})
bulk.execute(function(err, doc) {
if (err) {
return res.json({
'state': false,
'msg': err
})
}
res.json({
'state': true,
'msg': 'Followed'
})
})
})
Actual DB values
> db.follows.find().pretty()
{
"_id" : ObjectId("59e3e27dace1f14e0a70862d"),
"user" : ObjectId("59e2194177cae833894c9956"),
"following" : [
ObjectId("59e3e618ace1f14e0a708713")
]
}
{
"_id" : ObjectId("59e3e27dace1f14e0a70862e"),
"user" : ObjectId("59e13b2dca5652efc4ca2cf5"),
"followers" : [
ObjectId("59e2194177cae833894c9956"),
ObjectId("59e13b2d27cfed535928c0e7"),
ObjectId("59e3e617149f0a3f1281e849")
]
}
{
"_id" : ObjectId("59e3e71face1f14e0a708770"),
"user" : ObjectId("59e13b2d27cfed535928c0e7"),
"following" : [
ObjectId("59e3e618ace1f14e0a708713"),
ObjectId("59e13b2dca5652efc4ca2cf5"),
ObjectId("59e21942ca5652efc4ca30ab")
]
}
{
"_id" : ObjectId("59e3e71face1f14e0a708771"),
"user" : ObjectId("59e3e618ace1f14e0a708713"),
"followers" : [
ObjectId("59e13b2d27cfed535928c0e7"),
ObjectId("59e2194177cae833894c9956")
]
}
{
"_id" : ObjectId("59e3e72bace1f14e0a708779"),
"user" : ObjectId("59e21942ca5652efc4ca30ab"),
"followers" : [
ObjectId("59e13b2d27cfed535928c0e7"),
ObjectId("59e2194177cae833894c9956"),
ObjectId("59e3e617149f0a3f1281e849")
]
}
{
"_id" : ObjectId("59f0eef155ee5a5897e1a66d"),
"user" : ObjectId("59e3e617149f0a3f1281e849"),
"following" : [
ObjectId("59e21942ca5652efc4ca30ab"),
ObjectId("59e13b2dca5652efc4ca2cf5")
]
}
>
With the above database results, this is my query:
Query
router.get('/follow/list', utils.loginRequired, function(req, res) {
const user_id = req.user._id;
Follow.findOne({ 'user': Types.ObjectId(user_id) })
.populate('following')
.exec(function(err, doc) {
if (err) {
return res.json({
'state': false,
'msg': err
})
};
console.log(doc.username);
res.json({
'state': true,
'msg': 'Follow list',
'doc': doc
})
})
});
With the above query, from my little understanding of Mongoose populate, I expect to get cards from each of the Users in the following array.
My understanding and expectations might be wrong, however with such an endgoal, is this populate approach okay? Or am I trying to solve an aggregation task with population?
UPDATE:
Thanks for the answer. Getting quite close, but still, the followingCards array contains no result. Here's the contents of my current Follow model:
> db.follows.find().pretty()
{
"_id" : ObjectId("59f24c0555ee5a5897e1b23d"),
"user" : ObjectId("59f24bda1d048d1edad4bda8"),
"following" : [
ObjectId("59f24b3a55ee5a5897e1b1ec"),
ObjectId("59f24bda55ee5a5897e1b22c")
]
}
{
"_id" : ObjectId("59f24c0555ee5a5897e1b23e"),
"user" : ObjectId("59f24b3a55ee5a5897e1b1ec"),
"followers" : [
ObjectId("59f24bda1d048d1edad4bda8")
]
}
{
"_id" : ObjectId("59f24c8855ee5a5897e1b292"),
"user" : ObjectId("59f24bda55ee5a5897e1b22c"),
"followers" : [
ObjectId("59f24bda1d048d1edad4bda8")
]
}
>
Here are all the current content I have from Card Model:
> db.cards.find().pretty()
{
"_id" : ObjectId("59f24bc01d048d1edad4bda6"),
"title" : "A day or two with Hubtel's HTTP API",
"content" : "a day or two",
"external" : "",
"slug" : "a-day-or-two-with-hubtels-http-api-df77056d",
"createdBy" : "seanmavley",
"createdById" : ObjectId("59f24b391d048d1edad4bda5"),
"createdAt" : ISODate("2017-10-26T20:55:28.293Z"),
"__v" : 0
}
{
"_id" : ObjectId("59f24c5f1d048d1edad4bda9"),
"title" : "US couple stole goods worth $1.2m from Amazon",
"content" : "for what",
"external" : "https://bbc.com",
"slug" : "us-couple-stole-goods-worth-dollar12m-from-amazon-49b0a524",
"createdBy" : "nkansahrexford",
"createdById" : ObjectId("59f24bda1d048d1edad4bda8"),
"createdAt" : ISODate("2017-10-26T20:58:07.793Z"),
"__v" : 0
}
With the Populate Virtual example from yours (#Veeram), here's the response I get:
{"state":true,"msg":"Follow list","doc":{"_id":"59f24c0555ee5a5897e1b23d","user":"59f24bda1d048d1edad4bda8","following":["59f24b3a55ee5a5897e1b1ec","59f24bda55ee5a5897e1b22c"],"followers":[],"id":"59f24c0555ee5a5897e1b23d","followingCards":[]}}
The followingCards array is empty.
Using the $lookup query on the other hand simply returns []
I'm likely missing something?
You can use either virtual populate or $lookup operator in aggregation pipeline.
Using Virtual Populate
FollowSchema.virtual('followingCards', {
ref: 'Card',
localField: 'following',
foreignField: 'createdById'
});
Follow.findOne({
'user': Types.ObjectId(user_id) })
.populate('followingCards')
.exec(function(err, doc) {
console.log(JSON.stringify(doc));
});
Using $lookup aggregation
Follow.aggregate([
{
"$match": {
"user": Types.ObjectId(user_id)
}
},
{
"$lookup": {
"from": "cards",
"localField": "following",
"foreignField": "createdById",
"as": "followingCards"
}
}
]).exec(function (err, doc) {
console.log(JSON.stringify(doc));
})
var mongoose = require('mongoose'), Schema = mongoose.Schema
var eventSchema = Schema({
title : String,
location : String,
startDate : Date,
endDate : Date
});
var personSchema = Schema({
firstname: String,
lastname: String,
email: String,
dob: Date,
city: String,
eventsAttended: [{ type: Schema.Types.ObjectId, ref: 'Event' }]
});
var Event = mongoose.model('Event', eventSchema);
var Person = mongoose.model('Person', personSchema);
To show how populate is used, first create a person object,
aaron = new Person({firstname: 'Aaron'}) and an event object,
event1 = new Event({title: 'Hackathon', location: 'foo'}):
aaron.eventsAttended.push(event1);
aaron.save(callback);
Then, when you make your query, you can populate references like this:
Person
.findOne({ firstname: 'Aaron' })
.populate('eventsAttended') .exec(function(err, person) {
if (err) return handleError(err);
console.log(person);
});
// only works if we pushed refs to person.eventsAttended
note: change Activity.find to Card.find
const { ObjectID } = require("mongodb");
// import Follow and Activity(Card) schema
const userId = req.tokenData.userId; // edit this too...
Follow.aggregate([
{
$match: {
user: ObjectID(userId)
}
}
])
.then(data => {
// console.log(data)
var dataUsers = data[0].following.map(function(item) {
return item._id;
});
// console.log(dataUsers)
Activity.find(
{ createdById: { $in: dataUsers } },
{
_id: 1,
title: 1,
content: 1,
createdBy: 1,
creatorAvatar: 1,
activityType: 1,
createdAt: 1
}
)
// .sort({createdAt:-1)
.then(posts => res.send({ posts }));
});