Mongoose update the ENTIRE object inside a document with a nested array - mongodb

There are so many questions and answers regarding this subject but why not make it simple.
Branch schema
const Branch = new Schema({
name: { Type: String },
address: {
houseNumber: { Type: String },
street: { Type: String },
city: { Type: String }
}
})
Client schema
const Client = new Schema({
...,
...,
branches: [ branch ] // BRANCH SCHEMA IS SUB DOCUMENTED HERE
})
I know how to $push and $pull branch from branches array.
What I need is to UPDATE the ENTIRE branch object inside branches array, NOT JUST ONE FIELD like I found in so many answers and YES I would like to have back the modified document.
let clientId = req.body.clientId;
let branch = req.body.branch;
Client
.findOneAndUpdate(
{
"_id": clientId,
"branches._id": branch._id
},
{
OPTION 1 // MODIFIED ONLY THE FIRST ITEM (object) IN THE ARRAY
"$set:" { "branches.$": { branch } }
OPTION 2 // MODIFIED EVERY ITEM (object) IN THE ARRAY
"$set:" { "branches.$[]": { branch } }
STILL NO GOOD... HOW TO SOLVE THIS ??
}
)
.then(client => {
WHAT SHOULD I DO HERE IN ORDER TO UPDATE AN ENTIRE BRANCH ??
})
.catch(e => console.log(`error Client.findOne() ${e}`))

You can use mongoose arrayFilters to achieve what you want:
Client
.findOneAndUpdate(
{
"_id": clientId,
"branches._id": branch._id
},
{
"$set:" { "branches.$[elem]": { branch } }
},
{
arrayFilters: [ { 'elem._id': branch._id } ]
}
)
.then(client => {
})
.catch(e => console.log('error Client.findOne() + e))

Okay, here is how I did it..
let clientId = req.body.clientId;
let branch = req.body.branch;
Client
.findOne({ _id: clientId })
.then(client => {
// finding the index
const elementIndex = client.branches.findIndex(element => element._id.toString() === branch._id.toString());
// creating new array and assigning the branches array to it using spread syntax
let newBranches = [ ...client.branches ];
// adding the branch I need to update to newBranches array (at the same index)
newBranches[elementIndex] = branch;
// replacing branches array with the new one, again using spread syntax
client.branches = [ ...newBranches ];
// saving
client.save();
})
.catch(e => console.log(`error Client.findOne() ${e}`))
enjoy!

Related

Editing specific element of an array of a particular collection - mongoDb

I am new to mongoDb. I have created a collection named task that has comments field which is array along with other fields. I need to edit specific comment of the task. There is a edit button in each comment. Both task id and comment id are available. Now how to edit specific comment of the task?
Thanks in advance
task api
{
"status":true,
"task":[
{
"_id":"61dfef323a6ee474c4eba926",
"description":"hello there",
"title":"hello",
"comments":[
{
"comment_id":1,
"username":"test",
"comment":"abcd",
"status":true,
},
{
"comment_id":2,
"username":"test",
"comment":"abcdsdfsdf",
"status":true,
}
],
"createdAt":"2022-01-13T09:21:54.795Z",
"updatedAt":"2022-01-13T09:21:54.795Z",
"__v":0
}
]
}
Task model schema
const taskSchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
comments: [Object],
}, {
timestamps: true,
});
I tried using $set but I don't know how to use it in the inner array.
router.route('./comments/edit').post((req, res) => {
const commentId = req.body.commentId;
const taskId = req.body.postId;
const comment = req.body.editedComment;
const updatedAt = new Date();
Task.updateOne(
{ _id: taskId},
{
//what to do here?
// $set: { comments: [ {'comment_id': commentId} ]},
}
)
.then((response) => res.json({ status: true, msg: 'Comment Edited!' }))
.catch(err => res.json({ status: false, msg: err }));
});
Thanks in advance.
This is how to do best:
db.collection.update({
status: true
},
{
$set: {
"task.$[x].comments.$[y].username": "New Name"
}
},
{
arrayFilters: [
{
"x._id": "61dfef323a6ee474c4eba926"
},
{
"y.comment_id": 2
}
]
})
Explained:
Define x and y as arrayFIlters in the update statement.
In the $set statement provide the x & y filters to identify the specific comment for update.
In the example I update the username , but you can update any other value from the targeted array subelement addressed by x & y.
playground
And here is how to update two values at same time in the same nested array element.

Prisma splice Item from Array

I have been pushing updates to an array and was wondering if there is a built-in method to remove an entry from the array as well. Basically reversing the push command. I suspect that I have to query all documents and remove the item myself. But maybe there is some functionality I was unable to find inside the documentation.
Push:
const addTag = await prisma.post.update({
where: {
id: 9,
},
data: {
tags: {
push: 'computing',
},
},
})
Remove Expectation:
const removeTag = await prisma.post.update({
where: {
id: 9,
},
data: {
tags: {
splice: 'computing',
},
},
})
As of writing, there's no method to splice/remove items from a scalar list using Prisma. You would have to fetch the scalar list from your database, modify it manually in your application code and overwrite the record in your database with an update operation.
There is a feature request for this, please feel free to follow/comment with your use-case to help us track demand for this feature.
const { dogs } = await prisma.user.findOne({
where: {
id: userId
},
select: {
dogs: true
},
});
await prisma.user.update({
where: {
id: userId
},
data: {
dogs: {
set: dogs.filter((id) => id !== 'corgi'),
},
},
});

graphql.GraphQLSchema: what type of argument to use to grab the query to pass to mongo db.collection.find to resolve the query

I'm learning ho to develop GraphQL service with express, express-graphql, **graphql, mongoose,
db.collection.find has an optional query parameter that specifies selection filter using query operators.
I wonder if it is possible to define a schema in which to define an argument for a query field that ultimately it is passed as it is to the collection find methods.
for example I expect that the graphql query:
{ todosQuerable(query: {title: "Andare a Novellara"})
{ _id, title, completed }
}
responds with:
{
"data": {
"todos": [
{
"title": "Andare a Novellara",
"completed": false
}
]
}
}
since in mongo
> db.Todo.find({title: 'Andare a Novellara'})
{ "_id" : ObjectId("600d95d2e506988bc4430bb7"), "title" : "Andare a Novellara", "completed" : false }
I'm thinking something like:
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: <???????????????> },
},
resolve: (source, { query }) => {
return new Promise((resolve, reject) => {
TODO.find(query, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
I have made a few attempts but have not been able to get an idea of which type I should use in this case
ho help reproduce the problem here the source repository of my tests
Please note that this works fine:
todosByTitle: {
type: new graphql.GraphQLList(TodoType),
args: {
title: { type: graphql.GraphQLString },
},
resolve: (source, { title }) => {
return new Promise((resolve, reject) => {
TODO.find({title: {$regex: '.*' + title + '.*', $options: 'i'}}, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
but what I'm looking for is something more generic: I would like to grab graphql field argument named query and pass it as is to the the query parameter of the mongo collection find.
So the good news is you can do whatever you want. The bad news is that:
You have to do it yourself
You have to add every searchable field, so you'll probably end up with two copies of the Todo object here.
The type you're looking for is just a custom input object type like this:
Notice the GraphQLInputObjectType below is different from GraphQLObjectType.
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: graphql.GraphQLString
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
todosQuerable: {
...
type: new graphql.GraphQLList(TodoType),
...
args: {
query: { type: TodoQueryType },
},
...
}
These two queries work great!
(this is me using aliases so I can make the same query twice in one call)
{
titleSearch: todosQuerable(query:{ title:"Buy orange" }) {
_id
title
completed
}
idSearch: todosQuerable(query:{ _id:"601c3f374b6dcc601890048d" }) {
_id
title
completed
}
}
Footnote:
Just to have it said, this is generally a GraphQL anti-pattern, as this is building an API based on your database choices, rather than as a client-driven API.
Regex Edit as requested:
If you're trying to do regular expression lookups, you have to figure out how to programmatically convert your strings into regular expressions. i.e. your input is a string ("/Novellara/"), but mongoose requires passing a RegExp to do wildcards (/Novellara/, no quotes).
You can do that a number of ways, but I'll show one example. If you change your input fields to use two properties of value & isExpression, like below, you can do it, but you have to specifically craft your query, since it's no longer just a passthrough.
var ExpressableStringInput = new graphql.GraphQLInputObjectType({
name: 'ExpressableString',
fields: {
value: {
type: graphql.GraphQLString
},
isExpression:{
type: graphql.GraphQLBoolean,
defaultValue: false,
}
}
})
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: ExpressableStringInput
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
// resolver
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: TodoQueryType },
},
resolve: async (source, { query }) => {
const dbQuery = {};
if (query.title.isExpression) {
dbQuery.title = new RegExp(query.title.value);
} else {
dbQuery.title = query.title.value;
}
return new Promise((resolve, reject) => {
TODO.find(dbQuery, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
your query would then look like
query {
todosQuerable(query:{ title: { value: "Buy.*", isExpression: true }}) {
_id
title
completed
}
}
This query makes sense in my mind. If I think about the form you would show to a user, there is probably an input box and a checkbox that says "is this a regular expression?" or something, which would populate this query.
Alternatively, you could do like string matching: if the first and last characters are "/", you automagically make it into a regex before passing it into mongoose.

Mongoose findOneAndUpdate with $addToSet pushes duplicate

I have a schema such as
listSchema = new Schema({
...,
arts: [
{
...,
art: { type: Schema.Types.ObjectId, ref: 'Art', required: true },
note: Number
}
]
})
My goal is to find this document, push an object but without duplicate
The object look like
var art = { art: req.body.art, note: req.body.note }
The code I tried to use is
List.findOneAndUpdate({ _id: listId, user: req.myUser._id },
{ $addToSet: { arts: art} },
(err, list) => {
if (err) {
console.error(err);
return res.status(400).send()
} else {
if (list) {
console.log(list)
return res.status(200).json(list)
} else {
return res.status(404).send()
}
}
})
And yet there are multiple entries with the same Art id in my Arts array.
Also, the documentation isn't clear at all on which method to use to update something. Is this the correct way ? Or should I retrieve and then modify my object and .save() it ?
Found a recent link that came from this
List.findOneAndUpdate({ _id: listId, user: req.user._id, 'arts.art': artId }, { $set: { 'arts.$[elem]': artEntry } }, { arrayFilters: [{ 'elem.art': mongoose.Types.ObjectId(artId) }] })
artworkEntry being my modifications/push.
But the more I'm using Mongoose, the more it feels they want you to use .save() and modify the entries yourself using direct modification.
This might cause some concurrency but they introduced recently a, option to use on the schema { optimisticConcurrency: true } which might solve this problem.

The method findOneAndUpdate change the id of my element in a array

I'm working with mongoDB, mongoose and graphQL. I'm trying to make an update in my DB.
I'm doing an update in an array called phones, the changes work perfectly, the only problem is that when the update ends, the value of the objectId changes.
// Models -> Schema Organization
const organizationSchema = new mongoose.Schema({
name: String,
address: String,
phones: [
{
number: Number,
prefix: Number
}
],
email: String
})
// Types -> Organization
type Response {
success: Boolean!
token: String
errors: [Error]
}
type Error {
path: String!
message: String!
}
input iOrganization {
_id: ID
arrID: ID
address: String
email: String
number: Int
prefix: Int
name: String
}
type Mutation {
updateOrgGeneric(iOrg: iOrganization): Response!
}
// Resolvers -> Organization (1st way)
Mutation: {
updateOrgGeneric: (parent, args, {models}) => {
return models.Organization.findOneAndUpdate(
{ "_id": args.iOrg._id, "phones._id": args.iOrg.arrID },
{ $set: { "phones.$": { number: args.iOrg.number, prefix: args.iOrg.prefix }} },
{new: true}
)
.then((resp) => {
console.log(resp);
return {
success: true,
errors: []
}
})
.catch((error) => {
return {
success: false,
errors: error
};
})
},
}
// Resolvers -> Organization (2nd way)
Mutation: {
updateOrgGeneric: (parent, args, {models}) => {
return models.Organization.findOneAndUpdate(
{ "_id": args.iOrg._id },
{ $set: { "phones.$[arr]": { number: args.iOrg.number, prefix: args.iOrg.prefix }} },
{new: true}
{ arrayFilters:[{ "arr._id": mongoose.Types.ObjectId(args.iOrg.arrID) }], new: true}
)
.then((resp) => {
console.log(resp);
return {
success: true,
errors: []
}
})
.catch((error) => {
return {
success: false,
errors: error
};
})
}
}
// Playground (http://localhost:5000/graphql)
mutation {
updateOrgGeneric(
iOrg: {
_id: "5bdbee1b794b972bc8562aeb"
arrID: "5bdcea7cae88be098c020b19"
number: 85239,
prefix: 862
}
){
success
errors {
path
message
}
}
}
Both _id, as arrID, exist in the BD.
In the playground example the initial arrID was: _id:ObjectId("5bdcea7cae88be098c020b19"), but after the update is another, example: _id:ObjectId("5bdcec0a2ab78533b4bd1d98"). What am I doing wrong?
Thank you!
Mongodb is a nosql database which means that every object in the database should consist of an Id and revision values. Once an update occurs the revision value changes as part of the update process to implement the changes made to the data object. Since your data object don't have the revision value then the id value changes. Because it is unique. Now I'm no expert with mongo but you should check the docs on how to persist data objects and change accordingly
In case anyone lands here (despite this being old post), the problem probably lies in trying to update the entire phones object, of which the overwritten _id is a part. Since there's a model defined for phonesin mongoose, it will try to create a new _id any time an entire new phones object is created.
Someone who wanted to keep the same id would need to $set only the fields they want to change, rather than the entire object. So
{ $set: { "phones.$[arr]": { number: args.iOrg.number, prefix: args.iOrg.prefix }} }
could be changed to
{ $set: { "phones.$[arr].number": args.iOrg.number, "phones.$[arr].prefix": args.iOrg.prefix } }