GraphQL with Mongoose and MongoDB tips on deeply nested objects in arrays - mongodb

I can't find any answers or tips on how to work with deeply nested data with GraphQL and Mongoose. I would've thought this is a fairly common thing to do, but the docs are also pretty vague when it comes to stuff like this.
Here's how my data should look like. It is basically and a collection of invoices. Each invoice data for that invoice like customer name, invoice number, etc. It also contains an array of sections. Each section has data about that section like the type of products, color of products, etc. Each section itself contains an array of invoiceLines, and they all contain a product that takes properties from the section it is contained in, and also has it's own data.
Pseudo code:
{
"invoices": [
{
"_id": "123456",
"invoiceNumber": "i2022-123",
"customer": "John Doe",
"date": "2022-11-02",
"sections": [
{
"type": "phones",
"color": "red",
"invoiceLines": [
{
"product": "iPhone",
"year": "2022"
},
{
"product": "Samsung",
"year": "2021"
}
]
},
{
"type": "tablets",
"color": "black",
"invoiceLines": [
{
"product": "iPad",
"year": "2022"
},
{
"product": "Samsung tablet",
"year": "2021"
}
]
}
]
},
{
"Another": "Invoice"
}
]
}
My GraphQl queries look like so:
const query = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
getInvoices: {
type: new GraphQLList(ProjectType),
resolve(parent, args) {
return Project.find();
}
},
getInvoice: {
type: ProjectType,
args: { id: { type: GraphQLID } },
resolve(parent, args) {
return Project.findById(args.id);
}
}
}
});
Question #1: How would I query a specific section or an invoice line? they all have MongoDB IDs, but for some reason I can't use that to query them.
const { Project } = require('../../models/Project');
const { SectionType, SectionInputType } = require('../TypeDefs/SectionType');
const ProjectType = require('../TypeDefs/ProjectType');
const mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
// Add a Project
addProject: {
type: ProjectType,
args: {
date: { type: GraphQLString },
invoiceNumber: { type: GraphQLNonNull(GraphQLString) },
customer: { type: GraphQLNonNull(GraphQLString) },
},
resolve(parent, args) {
const project = new Project({
date: args.date,
invoiceNumber: args.invoiceNumber,
customer: args.customer,
sections: [],
})
return project.save()
}
},
// Add a Section
addSection: {
type: SectionType,
args: {
// MongoDB ID for the project the section belongs to
id: { type: GraphQLID },
section: { type: SectionInputType }
},
async resolve(parent, args) {
const newSection = args.section;
return await Project.updateOne({ _id: args.id }, {
$push: { sections: newSection }
})
}
},
}
});
I'm using $push to add a section to the invoice.sections and that works perfectly because I can get a hold of the invoice by the MongoDB ID.
Question #2: In that case how would I be able to add invoice lines to these sections that I add with this method, since I'm not able to get a hold of the sections by their respective _id.
I guess my main issue is that I'm not able to get a hold of nested MongoDB IDs.
Any help would be appreciated, or any pointers to good resources for GraphQL and Mongoose.
P.S. Yes, I'm new to GraphQL, but I like the concept of it so I wanted to explore it.
I've tried resources from YouTube and from graphQL docs, but pretty much everything is vague when it comes to a problem like this. I would think the deeply nested data like this is a common occurrence, but I can't find proper resources

Related

Can populate method in mongoose return a tree structure data

I have a model tree structures with parent references, this is my schema file:
export class Menu {
...
#Prop({ type: Types.ObjectId, ref: 'Menu' })
childrens: Menu[];
}
export const MenuSchema = SchemaFactory.createForClass(Menu);
some data in my database:
[
{
_id: ObjectId('menu1'),
name: 'menu1',
...
childrens: [ObjectId['menu1-1']],
},
{
_id: ObjectId('menu1-1'),
name: 'menu1-1',
...
childrens: [ObjectId['menu1-1-1']],
},
{
_id: ObjectId('menu1-1-1'),
name: 'menu1-1-1',
...
}
]
Now I use populte to query the data.
this.menuModel.findOne({_id:ObjectId('menu1')}).polulate('childrens');
It returns:
{
"_id": "menu1",
"name": "menu1",
...,
"childrens":[
{
"_id": "menu1-1",
"name": "menu1-1",
...
"childrens":["menu-1-1"]
}
]
}
the childrens in menu1-1 is not a Menu model list, just an ObjectId list, so why? The poplute method cannot build a tree structure?If not, how to let query to return a tree? I have try aggregate with $graphLookup method, it seems doesnot work too.

Update's request for mongodb [duplicate]

I have a document structure that is deeply nested, like this:
{id: 1,
forecasts: [ {
forecast_id: 123,
name: "Forecast 1",
levels: [
{ level: "proven",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
},
{ level: "likely",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
}
]
},
]
}
I'm trying to update the collection to insert a new config, that looks like this:
newdata = {
config: "Custom 1",
variables: [{ x: 111, y:2222, z:3333}]
}
I'm trying something like this in mongo (in Python):
db.myCollection.update({"id": 1,
"forecasts.forecast-id": 123,
"forecasts.levels.level": "proven",
"forecasts.levels.configs.config": "Custom 1"
},
{"$set": {"forecasts.$.levels.$.configs.$": newData}}
)
I'm getting "Cannot apply the positional operator without a corresponding query field containing an array" error though. What is the proper way to do this in mongo? This is mongo v2.4.1.
Unfortunately, you can't use the $ operator more than once per key, so you have to use numeric values for the rest. As in:
db.myCollection.update({
"id": 1,
"forecasts.forecast-id": 123,
"forecasts.levels.level": "proven",
"forecasts.levels.configs.config": "Custom 1"
},
{"$set": {"forecasts.$.levels.0.configs.0": newData}}
)
MongoDB's support for updating nested arrays is poor. So you're best off avoiding their use if you need to update the data frequently, and consider using multiple collections instead.
One possibility: make forecasts its own collection, and assuming you have a fixed set of level values, make level an object instead of an array:
{
_id: 123,
parentId: 1,
name: "Forecast 1",
levels: {
proven: {
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
},
likely: {
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
}
}
}
Then you can update it using:
db.myCollection.update({
_id: 123,
'levels.proven.configs.config': 'Custom 1'
},
{ $set: { 'levels.proven.configs.$': newData }}
)
Managed to solve it with using mongoose:
All you need to know is the '_id's of all of the sub-document in the chain (mongoose automatically create '_id' for each sub-document).
for example -
SchemaName.findById(_id, function (e, data) {
if (e) console.log(e);
data.sub1.id(_id1).sub2.id(_id2).field = req.body.something;
// or if you want to change more then one field -
//=> var t = data.sub1.id(_id1).sub2.id(_id2);
//=> t.field = req.body.something;
data.save();
});
More about the sub-document _id method in mongoose documentation.
explanation:_id is for the SchemaName, _id1 for sub1 and _id2 for sub2 - you can keep chaining like that.
*You don't have to use findById method, but it's seem to me the most convenient as you need to know the rest of the '_id's anyway.
MongoDB has introduced ArrayFilters to tackle this issue in Version 3.5.2 and later.
New in version 3.6.
Starting in MongoDB 3.6, when updating an array field, you can specify
arrayFilters that determine which array elements to update.
[https://docs.mongodb.com/manual/reference/method/db.collection.update/#specify-arrayfilters-for-an-array-update-operations][1]
Let's say the Schema design as follows :
var ProfileSchema = new Schema({
name: String,
albums: [{
tour_name: String,
images: [{
title: String,
image: String
}]
}]
});
And Document created looks like this :
{
"_id": "1",
"albums": [{
"images": [
{
"title": "t1",
"url": "url1"
},
{
"title": "t2",
"url": "url2"
}
],
"tour_name": "london-trip"
},
{
"images": [.........]:
}]
}
Say I want to update the "url" of an image.
Given - "document id", "tour_name" and "title"
For this the update query :
Profiles.update({_id : req.body.id},
{
$set: {
'albums.$[i].images.$[j].title': req.body.new_name
}
},
{
arrayFilters: [
{
"i.tour_name": req.body.tour_name, "j.image": req.body.new_name // tour_name - current tour name, new_name - new tour name
}]
})
.then(function (resp) {
console.log(resp)
res.json({status: 'success', resp});
}).catch(function (err) {
console.log(err);
res.status(500).json('Failed');
})
This is a very OLD bug in MongoDB
https://jira.mongodb.org/browse/SERVER-831
I was facing same kind of problem today, and after lot of exploring on google/stackoverflow/github, I figured arrayFilters are the best solution to this problem. Which would work with mongo 3.6 and above.
This link finally saved my day: https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html
const OrganizationInformationSchema = mongoose.Schema({
user: {
_id: String,
name: String
},
organizations: [{
name: {
type: String,
unique: true,
sparse: true
},
rosters: [{
name: {
type: String
},
designation: {
type: String
}
}]
}]
}, {
timestamps: true
});
And using mongoose in express, updating the name of roster of given id.
const mongoose = require('mongoose');
const ControllerModel = require('../models/organizations.model.js');
module.exports = {
// Find one record from database and update.
findOneRosterAndUpdate: (req, res, next) => {
ControllerModel.updateOne({}, {
$set: {
"organizations.$[].rosters.$[i].name": req.body.name
}
}, {
arrayFilters: [
{ "i._id": mongoose.Types.ObjectId(req.params.id) }
]
}).then(response => {
res.send(response);
}).catch(err => {
res.status(500).send({
message: "Failed! record cannot be updated.",
err
});
});
}
}
It's fixed.
https://jira.mongodb.org/browse/SERVER-831
But this feature is available starting with the MongoDB 3.5.12 development version.
Note: This question asked on Aug 11 2013 and it's resolved on Aug 11 2017
Given how MongoDB doesn't appear to provide a good mechanism for this, I find it prudent to use mongoose to simply extract the element from the mongo collection using .findOne(...), run a for-loop search on its relevant subelements (seeking by say ObjectID), modify that JSON, then do Schema.markModified('your.subdocument'); Schema.save(); It's probably not efficient, but it is very simple and works fine.
I searched about this for about 5 hours and finally found the best and easiest solution:
HOW TO UPDATE NESTED SUB-DOCUMENTS IN MONGO DB
{id: 1,
forecasts: [ {
forecast_id: 123,
name: "Forecast 1",
levels: [
{
levelid:1221
levelname: "proven",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
},
{
levelid:1221
levelname: "likely",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
}
]
},
]}
Query:
db.weather.updateOne({
"_id": ObjectId("1"), //this is level O select
"forecasts": {
"$elemMatch": {
"forecast_id": ObjectId("123"), //this is level one select
"levels.levelid": ObjectId("1221") // this is level to select
}
}
},
{
"$set": {
"forecasts.$[outer].levels.$[inner].levelname": "New proven",
}
},
{
"arrayFilters": [
{ "outer.forecast_id": ObjectId("123") },
{ "inner.levelid": ObjectId("1221") }
]
}).then((result) => {
resolve(result);
}, (err) => {
reject(err);
});
Sharing my lessons learned. I faced the same requirement recently where i need to update a nested array item.
My structure is as follows
{
"main": {
"id": "ID_001",
"name": "Fred flinstone Inc"
},
"types": [
{
"typeId": "TYPE1",
"locations": [
{
"name": "Sydney",
"units": [
{
"unitId": "PHG_BTG1"
}
]
},
{
"name": "Brisbane",
"units": [
{
"unitId": "PHG_KTN1"
},
{
"unitId": "PHG_KTN2"
}
]
}
]
}
]
}
My requirement is to add some fields in a specific units[].
My solution is first to find the index of the nested array item (say foundUnitIdx)
The two techniques I used are
use the $set keyword
specify the dynamic field in $set using the [] syntax
query = {
"locations.units.unitId": "PHG_KTN2"
};
var updateItem = {
$set: {
["locations.$.units."+ foundUnitIdx]: unitItem
}
};
var result = collection.update(
query,
updateItem,
{
upsert: true
}
);
Hope this helps others. :)
EASY SOLUTION FOR Mongodb 3.2+
https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/
I had a similar situation and solved it like this. I was using mongoose, but it should still work in vanilla MongoDB. Hope it's useful to someone.
const MyModel = require('./model.js')
const query = {id: 1}
// First get the doc
MyModel.findOne(query, (error, doc) => {
// Do some mutations
doc.foo.bar.etc = 'some new value'
// Pass in the mutated doc and replace
MyModel.replaceOne(query, doc, (error, newDoc) => {
console.log('It worked!')
})
}
Depending on your use case, you might be able to skip the initial findOne()
Okkk.we can update our nested subdocument in mongodb.this is our schema.
var Post = new mongoose.Schema({
name:String,
post:[{
like:String,
comment:[{
date:String,
username:String,
detail:{
time:String,
day:String
}
}]
}]
})
solution for this schema
Test.update({"post._id":"58206a6aa7b5b99e32b7eb58"},
{$set:{"post.$.comment.0.detail.time":"aajtk"}},
function(err,data){
//data is updated
})

Meteor Mongo add subdocument

I have a collection documents MasterPropinsis like this :
{
"_id": "4HSb7bbjFBzRSftXu",
"nama": "Yogyakarta",
"kabupaten": [
{
"id": "KulonProgo",
"nama": "Kulon Progo",
"kecamatan": [{ "nama": "Kalibawang" },{ "nama": "Nanggulan" }]
},
{
"id": "Sleman",
"nama": "Sleman",
"kecamatan": [{ "nama": "Depok" },{ "nama": "Berbah" }]
},
{
"id": "Bantul",
"nama": "Bantul",
"kecamatan": []
}
]
}
At kabupaten:Bantul, I want to Add subdocument kecamantan:XXX, with this code :
Masterpropinsis.update(
{
_id: Session.get('idKabupaten').toString(),
'kabupaten.id': Session.get('idKecamatan').replace(" ", "")
},
{
$addToSet: {
'kabupaten.kecamatan': {
nama: nama,
createdAt: new Date(),
createBy: CreateBy,
createByID: CreateByid
}
}
},
{
validate: true
});
But I get this error:
Uncaught Error: Not permitted. Untrusted code may only update documents by ID. [403]
Here is an example try this
var docId = Session.get('idKabupaten'); // usally toString is not nesserry unless you do something unusual
Masterpropinsis.update({_id: docId }, {
$push: {
'kabupaten.kecamatan': {
nama: nama,
createdAt: new Date(),
createBy: CreateBy,
createByID: CreateByid
}
}
}); // validate true also is the default unless again you do something unusual
you can see we only supply {_id: docId} in the selector the error says in client you can only update by the ID in the server you can do any selector. Also hopefully you can have Allow/Deny rules to allow updates from the client? http://docs.meteor.com/#/full/allow

Mongodb update deeply nested subdocument

I have a document structure that is deeply nested, like this:
{id: 1,
forecasts: [ {
forecast_id: 123,
name: "Forecast 1",
levels: [
{ level: "proven",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
},
{ level: "likely",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
}
]
},
]
}
I'm trying to update the collection to insert a new config, that looks like this:
newdata = {
config: "Custom 1",
variables: [{ x: 111, y:2222, z:3333}]
}
I'm trying something like this in mongo (in Python):
db.myCollection.update({"id": 1,
"forecasts.forecast-id": 123,
"forecasts.levels.level": "proven",
"forecasts.levels.configs.config": "Custom 1"
},
{"$set": {"forecasts.$.levels.$.configs.$": newData}}
)
I'm getting "Cannot apply the positional operator without a corresponding query field containing an array" error though. What is the proper way to do this in mongo? This is mongo v2.4.1.
Unfortunately, you can't use the $ operator more than once per key, so you have to use numeric values for the rest. As in:
db.myCollection.update({
"id": 1,
"forecasts.forecast-id": 123,
"forecasts.levels.level": "proven",
"forecasts.levels.configs.config": "Custom 1"
},
{"$set": {"forecasts.$.levels.0.configs.0": newData}}
)
MongoDB's support for updating nested arrays is poor. So you're best off avoiding their use if you need to update the data frequently, and consider using multiple collections instead.
One possibility: make forecasts its own collection, and assuming you have a fixed set of level values, make level an object instead of an array:
{
_id: 123,
parentId: 1,
name: "Forecast 1",
levels: {
proven: {
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
},
likely: {
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
}
}
}
Then you can update it using:
db.myCollection.update({
_id: 123,
'levels.proven.configs.config': 'Custom 1'
},
{ $set: { 'levels.proven.configs.$': newData }}
)
Managed to solve it with using mongoose:
All you need to know is the '_id's of all of the sub-document in the chain (mongoose automatically create '_id' for each sub-document).
for example -
SchemaName.findById(_id, function (e, data) {
if (e) console.log(e);
data.sub1.id(_id1).sub2.id(_id2).field = req.body.something;
// or if you want to change more then one field -
//=> var t = data.sub1.id(_id1).sub2.id(_id2);
//=> t.field = req.body.something;
data.save();
});
More about the sub-document _id method in mongoose documentation.
explanation:_id is for the SchemaName, _id1 for sub1 and _id2 for sub2 - you can keep chaining like that.
*You don't have to use findById method, but it's seem to me the most convenient as you need to know the rest of the '_id's anyway.
MongoDB has introduced ArrayFilters to tackle this issue in Version 3.5.2 and later.
New in version 3.6.
Starting in MongoDB 3.6, when updating an array field, you can specify
arrayFilters that determine which array elements to update.
[https://docs.mongodb.com/manual/reference/method/db.collection.update/#specify-arrayfilters-for-an-array-update-operations][1]
Let's say the Schema design as follows :
var ProfileSchema = new Schema({
name: String,
albums: [{
tour_name: String,
images: [{
title: String,
image: String
}]
}]
});
And Document created looks like this :
{
"_id": "1",
"albums": [{
"images": [
{
"title": "t1",
"url": "url1"
},
{
"title": "t2",
"url": "url2"
}
],
"tour_name": "london-trip"
},
{
"images": [.........]:
}]
}
Say I want to update the "url" of an image.
Given - "document id", "tour_name" and "title"
For this the update query :
Profiles.update({_id : req.body.id},
{
$set: {
'albums.$[i].images.$[j].title': req.body.new_name
}
},
{
arrayFilters: [
{
"i.tour_name": req.body.tour_name, "j.image": req.body.new_name // tour_name - current tour name, new_name - new tour name
}]
})
.then(function (resp) {
console.log(resp)
res.json({status: 'success', resp});
}).catch(function (err) {
console.log(err);
res.status(500).json('Failed');
})
This is a very OLD bug in MongoDB
https://jira.mongodb.org/browse/SERVER-831
I was facing same kind of problem today, and after lot of exploring on google/stackoverflow/github, I figured arrayFilters are the best solution to this problem. Which would work with mongo 3.6 and above.
This link finally saved my day: https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html
const OrganizationInformationSchema = mongoose.Schema({
user: {
_id: String,
name: String
},
organizations: [{
name: {
type: String,
unique: true,
sparse: true
},
rosters: [{
name: {
type: String
},
designation: {
type: String
}
}]
}]
}, {
timestamps: true
});
And using mongoose in express, updating the name of roster of given id.
const mongoose = require('mongoose');
const ControllerModel = require('../models/organizations.model.js');
module.exports = {
// Find one record from database and update.
findOneRosterAndUpdate: (req, res, next) => {
ControllerModel.updateOne({}, {
$set: {
"organizations.$[].rosters.$[i].name": req.body.name
}
}, {
arrayFilters: [
{ "i._id": mongoose.Types.ObjectId(req.params.id) }
]
}).then(response => {
res.send(response);
}).catch(err => {
res.status(500).send({
message: "Failed! record cannot be updated.",
err
});
});
}
}
It's fixed.
https://jira.mongodb.org/browse/SERVER-831
But this feature is available starting with the MongoDB 3.5.12 development version.
Note: This question asked on Aug 11 2013 and it's resolved on Aug 11 2017
Given how MongoDB doesn't appear to provide a good mechanism for this, I find it prudent to use mongoose to simply extract the element from the mongo collection using .findOne(...), run a for-loop search on its relevant subelements (seeking by say ObjectID), modify that JSON, then do Schema.markModified('your.subdocument'); Schema.save(); It's probably not efficient, but it is very simple and works fine.
I searched about this for about 5 hours and finally found the best and easiest solution:
HOW TO UPDATE NESTED SUB-DOCUMENTS IN MONGO DB
{id: 1,
forecasts: [ {
forecast_id: 123,
name: "Forecast 1",
levels: [
{
levelid:1221
levelname: "proven",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
},
{
levelid:1221
levelname: "likely",
configs: [
{
config: "Custom 1",
variables: [{ x: 1, y:2, z:3}]
},
{
config: "Custom 2",
variables: [{ x: 10, y:20, z:30}]
},
]
}
]
},
]}
Query:
db.weather.updateOne({
"_id": ObjectId("1"), //this is level O select
"forecasts": {
"$elemMatch": {
"forecast_id": ObjectId("123"), //this is level one select
"levels.levelid": ObjectId("1221") // this is level to select
}
}
},
{
"$set": {
"forecasts.$[outer].levels.$[inner].levelname": "New proven",
}
},
{
"arrayFilters": [
{ "outer.forecast_id": ObjectId("123") },
{ "inner.levelid": ObjectId("1221") }
]
}).then((result) => {
resolve(result);
}, (err) => {
reject(err);
});
Sharing my lessons learned. I faced the same requirement recently where i need to update a nested array item.
My structure is as follows
{
"main": {
"id": "ID_001",
"name": "Fred flinstone Inc"
},
"types": [
{
"typeId": "TYPE1",
"locations": [
{
"name": "Sydney",
"units": [
{
"unitId": "PHG_BTG1"
}
]
},
{
"name": "Brisbane",
"units": [
{
"unitId": "PHG_KTN1"
},
{
"unitId": "PHG_KTN2"
}
]
}
]
}
]
}
My requirement is to add some fields in a specific units[].
My solution is first to find the index of the nested array item (say foundUnitIdx)
The two techniques I used are
use the $set keyword
specify the dynamic field in $set using the [] syntax
query = {
"locations.units.unitId": "PHG_KTN2"
};
var updateItem = {
$set: {
["locations.$.units."+ foundUnitIdx]: unitItem
}
};
var result = collection.update(
query,
updateItem,
{
upsert: true
}
);
Hope this helps others. :)
EASY SOLUTION FOR Mongodb 3.2+
https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/
I had a similar situation and solved it like this. I was using mongoose, but it should still work in vanilla MongoDB. Hope it's useful to someone.
const MyModel = require('./model.js')
const query = {id: 1}
// First get the doc
MyModel.findOne(query, (error, doc) => {
// Do some mutations
doc.foo.bar.etc = 'some new value'
// Pass in the mutated doc and replace
MyModel.replaceOne(query, doc, (error, newDoc) => {
console.log('It worked!')
})
}
Depending on your use case, you might be able to skip the initial findOne()
Okkk.we can update our nested subdocument in mongodb.this is our schema.
var Post = new mongoose.Schema({
name:String,
post:[{
like:String,
comment:[{
date:String,
username:String,
detail:{
time:String,
day:String
}
}]
}]
})
solution for this schema
Test.update({"post._id":"58206a6aa7b5b99e32b7eb58"},
{$set:{"post.$.comment.0.detail.time":"aajtk"}},
function(err,data){
//data is updated
})

MongoDB: How to find by subdocument ID?

I am trying to model the concept of games where teams of players compete against each other in MongoDB.
I have two collections: players and games.
This is how a document in games looks like.
{
"_id": { "$oid": "1" },
"teams": [
{
"players": [
{
"player": { "$oid": "2" },
"score": 500,
},
{
"player": { "$oid": "3" },
"score": 550,
}
]
},
{
"players": [
{
"player": { "$oid": "4" },
"score": 500,
},
{
"player": { "$oid": "5" },
"score": 550,
}
]
}
]
}
Here's the task: given a player ID I want to find all games in which this player participated.
What I've tried:
db.games.find( { "teams.players.player._id": "2" } )
However, this does not return anything.
By the way, I'm using Mongoose with the following schema:
playerSchema = Schema
player: { type: Schema.ObjectId, ref: 'Player' }
score: { type: Number }
teamSchema = Schema
players: [ playerSchema ]
gameSchema = Schema
teams: [ teamSchema ]
with the following CoffeeScript query:
Game.find 'teams.players.player._id': playerId
which returns no results for any player ID.
In your document:
"players": [
{
"player": { "$oid": "4" },
"score": 500,
},
{
"player": { "$oid": "5" },
"score": 550,
}
]
The player field in the embedded collection of players is a BSON Id (i.e. it looks something like ObjectId("4e208e070347a90001000008")), so I think you should structure your query like so:
db.games.find( { "teams.players.player": ObjectId("2") } )
Note, I've dropped the _id -- provided that works in a mongo console, then I suspect the Coffee query will be similar (drop the _id portion).
I used your feedback to restrict this example to a home and away team. Remember that Mongo is not a relational database and using relationships is often an indication of a relational-database mindset. Duplication is the key.
Instead of references from the game to specific player documents, I have stored the name of the player. I assume this is an immutable unique index. The player has been given a games array containing references to each game he played in. This is actually quite bad as we have to query again and populate this array. It would be better to store a name here as well, but since I don't know your situation I'm not sure if a game has an immutable representable name.
This basic idea has to be improved with error checking (e.g. middleware) but I leave that up to you.
// Schemas
var playerSchema = new Schema({
name: {type: String, index: true, unique: true},
games: {type: [Schema.ObjectId], ref: 'Game'}
});
var gameSchema = new Schema({
homeTeam: [{player: {name: String}, score: Number}]
awayTeam: [{player: {name: String}, score: Number}]
});
// Models
var Player = mongoose.model('Player', playerSchema);
var Team = mongoose.model('Team', teamSchema);
var Game = mongoose.model('Game', gameSchema);
// Middleware
gameSchema.post('save', function (game) {
var addMatchToPlayer = function(name) {
Player.findOne({name: name}, function(err, player) {
if (!err && player && player.games.indexOf(game._id) === -1) {
player.games.push(game._id);
player.save();
}
});
}
for (var i = 0; i < game.homeTeam.length; i++) {
addMatchToPlayer(game.homeTeam[i].name);
}
for (var i = 0; i < game.awayTeam.length; i++) {
addMatchToPlayer(game.awayTeam[i].name);
}
});
// Queries
Player.findOne({name: 'Roel van Uden'}).populate('games').exec(function (err, player) {
});