I am using react-sortable-hoc to drag/drop and reorder items. When I do this though I want to update the database (node.js and mongodb).
Firstly, I have changed it to be a functional component which is why my syntax looks a bit different to the example.
const onSortEnd = ({ oldIndex, newIndex }) => {
setItems((items) => arrayMove(items, oldIndex, newIndex));
const newArray = arrayMove(items, oldIndex, newIndex);
async function makePatchRequest() {
const config = {
method: "patch",
url: "http://localhost:8000/api/admin/faq/order",
headers: { Authorization: "Bearer " + auth.token },
data: {
order: newArray,
},
};
let res = await axios(config, { order: newArray });
}
makePatchRequest();
};
I am sending the new array to the backend with everything in the order after the drag and drop. The issue is that I don't really know what to do with it on the backend. Do I need to delete all records and then loop over the array and insert new records? I initially wanted to loop over the records and update them but it isn't actually doing anything, probably because the code is wrong or my logic is wrong because all it is doing is overwriting with the exact same data because all that has changed is the order of the array, not the actual id or _id in the array.
exports.faqSort = async (req, res) => {
const { order } = req.body;
console.log(order);
await order.map((o) => {
Faq.update({ _id: o._id }, { $set: { id: o.id } });
});
};
This is the array when the page loads:
[
{
_id: '5ed273049b268308302cb1fb',
question: 'question 1',
answer: 'answer 1',
id: 1,
__v: 0
},
{
_id: '5ed273439b268308302cb1fd',
question: 'question 2',
answer: 'answer 2',
id: 2,
__v: 0
},
{
_id: '5ed276129b268308302cb1ff',
question: 'quesiton 3',
answer: 'answer 3',
id: 3,
__v: 0
}
]
And this is the new array I send to the backend
[
{
_id: '5ed276129b268308302cb1ff',
question: 'quesiton 3',
answer: 'answer 3',
order: 3,
__v: 0
},
{
_id: '5ed273049b268308302cb1fb',
question: 'question 1',
answer: 'answer 1',
order: 1,
__v: 0
},
{
_id: '5ed273439b268308302cb1fd',
question: 'question 2',
answer: 'answer 2',
order: 2,
__v: 0
}
]
If you updating the documents, use the index values from the array to update the order id.
const promises = order.map( async (o, index) => {
let orderkey = index + 1;
const promise = Faq.updateOne({ _id: o._id }, { $set: { order: orderkey } });
return promise;
});
const results = await Promise.all(promises);
This way the order key will be updated based on the order of the array you are sending .
But think about actually updating the order key already in the frontend, before sending it to the backend. This way the order of the array wouldn't matter.
Simplest and safest way I think would just be to use "order" key in the objects held by the array. You could just keep the array in any order, instead using the "order" key to handle, well, the order.
You can always manipulate your array with the built-in sort() method, or query this the "order" key in Mongoose.
Basically, don't use the array's order, but your own instead.
Related
I have a structure like this:
{
_id: new ObjectId("634aa49f98e3a05346dd2327"),
filmName: 'Film number 1',
episodes: [
{
episodeName: 'Testing 1',
slugEpisode: 'testing-1',
_id: new ObjectId("6351395c17f08335f1dabfc9")
},
{
episodeName: 'Testing 2',
slugEpisode: 'testing-2',
_id: new ObjectId("6351399d9a2533b9be1cbab0")
},
],
},
{
_id: new ObjectId("634aa4cc98e3a05346dd232a"),
filmName: 'Film number 2',
episodes: [
{
episodeName: 'Something 1',
slugEpisode: 'something-1',
_id: new ObjectId("6367cce66d6b85442f850b3a")
},
{
episodeName: 'Something 2',
slugEpisode: 'something-2',
_id: new ObjectId("6367cd0e6d6b85442f850b3e")
},
],
}
I received 3 fields:
_id: Film _id
episodeId: Episode _id
episodeName: The content I wish to update
I tried to find a specific Film ID to get a specific film, and from then on, I pass an Episode ID to find the exact episode in the episodes array. Then, update the episodeName of that specific episode.
Here's my code in NodeJS:
editEpisode: async (req, res) => {
const { _id } = req.params
const { episodeId, episodeName } = req.body
try {
const specificResult = await Films.findOneAndUpdate(
{ _id, 'episodes._id': episodeId },
{ episodeName }
)
console.log(specificResult)
res.json({ msg: "Success update episode name" })
} catch (err) {
return res.status(500).json({ msg: err.message })
}
},
But what console.log display to me is a whole document, and when I check in MongoDB, there was no update at all, does my way of using findOneAndUpdate incorrect?
I'm reading this document: MongooseJS - Find One and Update, they said this one gives me the option to filter and update.
The MongoDB server needs to know which array element to update. If there is just one array element to update, here's one way you could do it. (I picked a specific element. You would use your req.params and req.body.)
db.films.update({
"_id": ObjectId("634aa4cc98e3a05346dd232a"),
"episodes._id": ObjectId("6367cd0e6d6b85442f850b3e")
},
{
"$set": {
"episodes.$.episodeName": "Something Two"
}
})
Try it on mongoplayground.net.
You can use the filtered positional operator $[<identifier>] which essentially finds the element or object (in your case) with a filter condition and updates that.
Query:
const { _id } = req.params
const { episodeId, episodeName } = req.body
await Films.update({
"_id": _id
},
{
$set: {
"episodes.$[elem].episodeName": episodeName
}
},
{
arrayFilters: [
{
"elem._id": episodeId
}
]
})
Check it out here for example purpose I've put ids as numbers and episode name to update as "UpdatedValue"
Getting error when accesing the data by using sort method
Error is
No index exists for this sort
Create index code is
db.createIndex({
index: { fields: ["timestamp", "upVote"] },
}).then(() => {
this.intitialDatafromDB(db);
});
find function is
db.find({
selector: { upVote: { $gt: 5 } },
sort: [{ upVote: "desc" }],
fields: ["news"],
}).then((result: any) => {
this.data = result.docs;
}).catch((err: any) => {
console.log(err);
});
The reason your query is failing is that the order of index fields matters.
From the pouchdb documentation Indexing on more than one field
One thing to note is that the order of these fields matters when
creating your index
By specifying the index as so
fields: ['timestamp','upVote']
The index looks like this
timestamp
upVote
1590399369500
3
1590399369600
4
1590399369700
1
1590399369700
2
1590399369700
3
1590399369800
1
Note the timestamp 1590399369700, and how the secondary field upVote sorts.
If your index fields were ordered like so
fields: ['upVote','timestamp']
Given the theoretical data above, the index would look like this
upVote
timestamp
1
1590399369700
1
1590399369800
2
1590399369700
3
1590399369500
3
1590399369700
4
1590399369600
and your query would return the results you expect, as is demonstrated in the snippet below. I recommend reading over Map/reduce queries; grasping the concepts presented in that documentation will provide a deeper understanding of why this is so.
const g_result = 'result';
const getEl = id => document.getElementById(id);
let db;
async function view() {
const view = getEl(g_result);
const result = await db.find({
selector: {
upVote: {
$gt: 5
},
},
sort: [{
'upVote': 'desc'
}],
fields: ['news','upVote']
}, );
view.innerText = JSON.stringify(result, undefined, 3);
}
// canned test documents
function getDocsToInstall() {
return [{
timestamp: 1590399369508,
upVote: 3,
news: "new item 1"
},
{
timestamp: 1590399248600,
upVote: 4,
news: "new item 2"
},
{
timestamp: 1590399248600,
upVote: 5,
news: "new item 3"
},
{
timestamp: 1590399248700,
upVote: 6,
news: "new item 4"
},
{
timestamp: 1590399248900,
upVote: 7,
news: "new item 5"
},
{
timestamp: 1590399249000,
upVote: 8,
news: "new item 6"
},
]
}
// init example db instance
async function initDb() {
db = new PouchDB('test', {
adapter: 'memory'
});
await db.bulkDocs(getDocsToInstall());
await db.createIndex({
index: {
fields: ['upVote', 'timestamp']
}
});
};
(async() => {
await initDb();
await view();
})();
https: //stackoverflow.com/questions/69122670/no-index-exists-for-this-sort-couchdb#
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb-7.1.1.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.find.min.js"></script>
<pre id='view'></pre>
<div style='margin-top:2em'></div>
<pre id='result'>
</pre>
I am trying to find and check if that user has a value on a certain item.
Sample model data:
user: {
_id: 1,
profilePictures: [
{
id: 1,
image: 'sample.png'
},
{
id: 2,
image: null
},
{
id: 3,
image: null
}
]
}
My Mongoose.find query:
const user = await userModel.findOne({
_id: 1,
profilePictures: {
$elemMatch: {
$and: [
{ id: 2, image: null },
{ id: 3, image: null }
]
}
}
})
but its returning nothing, I know i could just get the user and use Array.some() on profilePictures or some other Array function to check if an array item with position 2 and 3 has also a property image with a value of null
but since I'm learning Mongoose i am trying to find a way to do it that way.
Assuming you need to find the document for particular indexes, and the array is just one level, you could use this simple query
db.getCollection('somecollection').find({ "_id": 1, "profilePictures.1.image": null, "profilePictures.2.image": null })
So for now, I figured out a solution that doesn't use a complicated mongoose query.
I just validated it using JavaScript.
// req.body.id is an array of number
const user: any = await db.getCollection('somecollection').findOne({ _id: 1 })
let isAllPositionAvailable = true
for (const position of req.body.id) {
isAllPositionAvailable = user.profilePictures.find((x: any) => x.id === parseInt(position)).image === null
if (!isAllPositionAvailable) break
}
if (!isAllPositionAvailable) {
return res.status(400).send({
message: 'Image position is not available, please remove the existing image first'
})
}
router.delete('/shopping-cart/:id', (req, res) => {
let cart = new Cart(req.session.cart);
console.log(req.params.id);
console.log(cart.generateArray());
});
console.log will output the following result (req.params.id):
5c863cc8ee0819f989acf9c3
console.log will output the following result (cart.generateArray()):
[ { item:
{ _id: '5c863cc8ee0819f989acf9c3',
imagePath: 'https://upload.wikimedia.org/wikipedia/en/5/5e/Gothiccover.png',
title: 'Gothic Video',
description: 'Absolutely stunning',
price: 10,
__v: 0 },
image: 'https://upload.wikimedia.org/wikipedia/en/5/5e/Gothiccover.png',
qty: 1,
price: 10,
id: '5c863cc8ee0819f989acf9c3' } ]
So how do I loop through all the items and check if id matches the req.params.id. If that is the case, it should remove that object and then return an updated array on the client side.
let cards = [{
item: {
_id: '5c863cc8ee0819f989acf9c3',
imagePath: 'https://upload.wikimedia.org/wikipedia/en/5/5e/Gothiccover.png',
title: 'Gothic Video',
description: 'Absolutely stunning',
price: 10,
__v: 0,
},
image: 'https://upload.wikimedia.org/wikipedia/en/5/5e/Gothiccover.png',
qty: 1,
price: 10,
id: '5c863cc8ee0819f989acf9c3',
}]
cards.forEach(element => {
delete element.id
})
console.info(cards)
I assume that is coming from cart.generateArray()?
If so, you probably should do that inside the function in question, but if you can't, then just map the results to a new array:
let result = cart.generateArray().map(item => {
const {id, ...entry} = item;
// if you want to also remove _id from the inner item, you can do the same
return entry;
});
console.log(result); // will have entries w/o id in them.
If you're looking to remove the nested item object as well, it's the smae approach, though I'll change some words to improve readability.
let result = cart.generateArray().map(cartItem => {
const {id, item, ...entry} = cartItem;
// this will remove both the id and the item keys
return entry;
});
console.log(result); // will have entries w/o id in them.
Is there a way to update values in an object?
{
_id: 1,
name: 'John Smith',
items: [{
id: 1,
name: 'item 1',
value: 'one'
},{
id: 2,
name: 'item 2',
value: 'two'
}]
}
Lets say I want to update the name and value items for item where id = 2;
I have tried the following w/ mongoose:
var update = {name: 'updated item2', value: 'two updated'};
Person.update({'items.id': 2}, {'$set': {'items.$': update}}, function(err) { ...
Problem with this approach is that it updates/sets the entire object, therefore in this case I lose the id field.
Is there a better way in mongoose to set certain values in an array but leave other values alone?
I have also queried for just the Person:
Person.find({...}, function(err, person) {
person.items ..... // I might be able to search through all the items here and find item with id 2 then update the values I want and call person.save().
});
You're close; you should use dot notation in your use of the $ update operator to do that:
Person.update({'items.id': 2}, {'$set': {
'items.$.name': 'updated item2',
'items.$.value': 'two updated'
}}, function(err) { ...
model.update(
{ _id: 1, "items.id": "2" },
{
$set: {
"items.$.name": "yourValue",
"items.$.value": "yourvalue",
}
}
)
MongoDB Document
There is a mongoose way for doing it.
const itemId = 2;
const query = {
item._id: itemId
};
Person.findOne(query).then(doc => {
item = doc.items.id(itemId );
item["name"] = "new name";
item["value"] = "new value";
doc.save();
//sent respnse to client
}).catch(err => {
console.log('Oh! Dark')
});
There is one thing to remember, when you are searching the object in array on the basis of more than one condition then use $elemMatch
Person.update(
{
_id: 5,
grades: { $elemMatch: { grade: { $lte: 90 }, mean: { $gt: 80 } } }
},
{ $set: { "grades.$.std" : 6 } }
)
here is the docs
For each document, the update operator $set can set multiple values, so rather than replacing the entire object in the items array, you can set the name and value fields of the object individually.
{'$set': {'items.$.name': update.name , 'items.$.value': update.value}}
Below is an example of how to update the value in the array of objects more dynamically.
Person.findOneAndUpdate({_id: id},
{
"$set": {[`items.$[outer].${propertyName}`]: value}
},
{
"arrayFilters": [{ "outer.id": itemId }]
},
function(err, response) {
...
})
Note that by doing it that way, you would be able to update even deeper levels of the nested array by adding additional arrayFilters and positional operator like so:
"$set": {[`items.$[outer].innerItems.$[inner].${propertyName}`]: value}
"arrayFilters":[{ "outer.id": itemId },{ "inner.id": innerItemId }]
More usage can be found in the official docs.
cleaner solution using findOneAndUpdate
await Person.findOneAndUpdate(
{ _id: id, 'items.id': 2 },
{
$set: {
'items.$.name': 'updated item2',
'items.$.value': 'two updated',
}
},
);
In Mongoose, we can update array value using $set inside dot(.) notation to specific value in following way
db.collection.update({"_id": args._id, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
Having tried other solutions which worked fine, but the pitfall of their answers is that only fields already existing would update adding upsert to it would do nothing, so I came up with this.
Person.update({'items.id': 2}, {$set: {
'items': { "item1", "item2", "item3", "item4" } }, {upsert:
true })
I had similar issues. Here is the cleanest way to do it.
const personQuery = {
_id: 1
}
const itemID = 2;
Person.findOne(personQuery).then(item => {
const audioIndex = item.items.map(item => item.id).indexOf(itemID);
item.items[audioIndex].name = 'Name value';
item.save();
});
Found this solution using dot-object and it helped me.
import dot from "dot-object";
const user = await User.findByIdAndUpdate(id, { ...dot.dot(req.body) });
I needed to update an array element with dynamic key-value pairs.
By mapping the update object to new keys containing the $ update operator, I am no longer bound to know the updated keys of the array element and instead assemble a new update object on the fly.
update = {
name: "Andy",
newKey: "new value"
}
new_update = Object.fromEntries(
Object.entries(update).map(
([k, v], i) => ["my_array.$." + k, v]
)
)
console.log({
"$set": new_update
})
In mongoose we can update, like simple array
user.updateInfoByIndex(0,"test")
User.methods.updateInfoByIndex = function(index, info) ={
this.arrayField[index]=info
this.save()
}
update(
{_id: 1, 'items.id': 2},
{'$set': {'items.$[]': update}},
{new: true})
Here is the doc about $[]: https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up.S[]