Store array of string into the mongoDB collection - mongodb

I am very new to MongoDB and mongoose I have a model name called agent
agent.model.js
const mongoose = require('mongoose')
const agentSchema = new mongoose.Schema({
agent: {
type: String,
required: true
}
})
const Agent = mongoose.model('Agent', agentSchema)
module.exports = Agent;
Now I have a array of string:
const agentName = ['john', 'alex', 'david'];
Now I want to store this array into the mongoDB as an individual agent.
like this:
[
{
"_id": "6000977d9b94f52960955066",
"agent": "john",
"__v": 0
},
{
"_id": "6000977d9b94f52960955067",
"agent": "alex",
"__v": 0
},
{
"_id": "6000977d9b94f52960955068",
"agent": "david",
"__v": 0
}
]
Note: Right now First I am converting my array of string into the array of object using loop like this:
agentName = agentName.map((e) => {return {agent: e}})
//output of above line of code
[ { agent: 'Alex Watson' },
{ agent: 'John Snow' },
{ agent: 'Rita Ora' } ]
Then I am saving the agentName.
But I am looking for some better approach, Like in which there is no need of converting the array of string into the array of object.

you must to use insertMany() function is used to insert multiple documents into a collection. It accepts an array of documents to insert into the collection. like following code, so have to create a array of abjects, that you created
note: in your question define const agentName next step assign result of map to the constant variable, so this is wrong
const agentName = ['john', 'alex', 'david'];
let arr = agentName.map((e) => {return {agent: e}})
Agent.insertMany(arr).then(function(){
console.log("Data inserted") // Success
}).catch(function(error){
console.log(error) // Failure
});

Related

Mongo DB - How to get only the inserted Object in an array when calling collection.watch()

I am having a little problem with my code.
I have a collection called user_relations and there I save friends that a specific user has.
One document example:
So my question is: How can I only get the Object item that has been added to arrayOfFriends without getting all of the other values inside that array and outside. For example:
If I insert {"userId" : "2", "lastMessage" : "Hello"} into arrayOfFriends, I want that my response looks like this: {"userId" : "2", "lastMessage" : "Hello"} and not like this {"_id" : ObjectId("id..."), "arrayOfFriends" : {...}}.
The code that I am currently using:
var stream = db.collection('user_relations').watch(<Map<String, Object>>[
{
'$match': {
'$and': [
{'operationType': 'insert'},
{'fullDocument.userId': '6fcfd7b3847dd9999430f1ad'}
]
}
}
]);
stream.listen((changeEvent) {
Map fullDocument = changeEvent.fullDocument;
print('fullDocument: $fullDocument');
// Insert your logic here
});
The code is written in flutter, but I think it is pretty similar to Node.js and Python and can be read easily.
Thank You!
I believe the OP was asking about how to create this using Flutter. Nonetheless here is a node example not using mongoose, but only using the raw driver...
npm init -y
npm install mongodb
Create file app.js
const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
const uri = "mongodb://barry:barry#localhost:50011,localhost:50012,localhost:50013/nodetest?replicaSet=replSet&authSource=admin";
const client = new MongoClient(uri, { useUnifiedTopology: true });
const collectionName = "user_relations";
client.connect(function(err) {
const db = client.db("nodetest");
const collection = db.collection(collectionName);
const pipeline = [
{
"$match": {
"operationType": "insert",
"fullDocument.userId": "6fcfd7b3847dd9999430f1ad"
}
}
];
const stream = collection.watch(pipeline);
const changeStreamOptions = { fullDocument: "updateLookup" };
stream.on("change", stream_OnChange, changeStreamOptions);
});
function stream_OnChange(documentChange) {
console.log(documentChange.fullDocument.arrayOfFriends);
}
Probably need to change the connection string, and/or database names.
Execute using nodemon...
nodemon app.js
Insert into the MongoDB database using mongoshell...
db.user_relations.insert({userId: "6fcfd7b3847dd9999430f1ad", arrayOfFriends: [ {userId: 1, lastMessage: "Message 1"}, {userId: 1, lastMessage: "Message 2"}] })
See output in nodemon window
[nodemon] starting `node app.js`
[
{ userId: 1, lastMessage: 'Message 1' },
{ userId: 1, lastMessage: 'Message 2' }
]
Conclusion and Evaluation
Notice the output is only part of the full document? This is because the stream_OnChange method is referring to the sub fields directly. Is this what you are after?
EDIT 2021-08-29
Per comments by the OP, the desire is to see an item that is added to an array, presumably by a $push operation, but show only that item. The strategy for this is to NOT look at the object "fullDocument" but instead look at the operation in the changestream.
To illustrate this, I have modified the function stream_OnChange(documentChange) as described above in the file app.js.
function stream_OnChange(documentChange) {
console.log("##### FULL PAYLOAD #####");
console.log(documentChange);
console.log("##### END FULL PAYLOAD #####");
try {
var updatedFields = documentChange.updateDescription.updatedFields;
Object.keys(updatedFields).forEach( function (key1, index1, _array1) {
var value1 = updatedFields[key1];
console.log(key1 + ": " + value1);
Object.keys(value1).forEach( function (key2, index2, _array2) {
var value2 = value1[key2];
console.log(key2 + ":" + value2);
});
});
}
catch(e) {
}
}
Here, I am parsing the response of the update peeling apart the updateDescription and looking specifically at the updatedFields.
For example, lets say I push a new object to my array...
db.user_relations.updateOne({ userId: "6fcfd7b3847dd9999430f1ad" }, { $push: { arrayOfFriends: { userId: 1, lastMessage: "Message 3" } } }
)
Example of output:
##### FULL PAYLOAD #####
{
_id: {
_data: '82612BD118000000012B022C0100296E5A10041B035F339E2B48B6A3A5E707D801316A46645F69640064612BD10D69B433970757F4EF0004'
},
operationType: 'update',
clusterTime: new Timestamp({ t: 1630261528, i: 1 }),
ns: { db: 'nodetest', coll: 'user_relations' },
documentKey: { _id: new ObjectId("612bd10d69b433970757f4ef") },
updateDescription: {
updatedFields: { 'arrayOfFriends.2': [Object] },
removedFields: [],
truncatedArrays: []
}
}
##### END FULL PAYLOAD #####
arrayOfFriends.2: [object Object]
userId:1
lastMessage:Message 3
To catch updates I needed to modify app.js client connect method - specifically the pipeline definition...
client.connect(function(err) {
const db = client.db("nodetest");
const collection = db.collection(collectionName);
const pipeline = [
{
"$match": {
"operationType": "update"
}
}
];
const stream = collection.watch(pipeline);
const changeStreamOptions = { fullDocument: "updateLookup" };
stream.on("change", stream_OnChange, changeStreamOptions);
});

UpdateOne is not working with array of objects in mongoDB

I try to update an object of an array in mongoDB. When the variable listID is not defined, it'll just update the first object of the array (but that's not what I want).
My goal is to add the word IDs, update the last_practiced field and increment the number of the word count field by the number of wordIDs.
I tried it with aggregation as well, but I couldn't get it to work.
My current query
Words.prototype.updatePersonalWordLists = async function(userID, listData) {
const listID = listData.list_id
const wordIDs = listData.word_ids
const last_practiced = listData.last_practiced
const numberOfNewWords = listData.word_ids.length
const lists = await personalWordLists.updateOne(
{"user_id": userID, "lists.$.list_id": listID },
{
$addToSet: { "lists.$.practiced_words": { $each: wordIDs}},
$set: {
"lists.$.last_practiced": last_practiced,
$inc: {"lists.$.wordCount": numberOfNewWords}
}
}
)
return lists
}
let userID = "609b974f6bd8dc6019d2f304"
let listData = {
list_id: "609d22188ea8aebac46f9dc3",
last_practiced: "03-04-2021 13:25:10",
word_ids: ["1", "2", "3", "4", "5"],
}
word.updatePersonalWordLists(userID, listData).then(res => console.log(res))
My scheme
const mongoose = require('mongoose');
const personalWordListsSchema = new mongoose.Schema({
user_id: {
type: String
},
lists: Array
});
module.exports = personalWordListsSchema
I hope someone can help me to figure this out. Thanks in advance.
Thanks to #joe's comment I was able to make it work.
I removed the $ from the filter portion and used the ´´´$inc´´´ outside of the ´´´$set´´´ portion.
Word IDs are pushed only if they are unique.
Date of last practice can be updated.
Word count increments too.
const list = await personalWordLists.updateOne(
{"user_id": userID, "lists.list_id": listID },
{
$addToSet: { "lists.$.practiced_words": { $each: wordIDs}},
$set: {
"lists.$.last_practiced": last_practiced
},
$inc: {"lists.$.wordCount": numberOfNewWords}
}
)
return list

Mongoose populate method not populating all fields after updating document

I change the order of an array and then populate doesn't work. It still works on the documents that I did not try to update.
I have a seccion model with preguntas inside of it
SECCION MODEL
const Schema = mongoose.Schema;
const seccionSchema = new Schema({
titulo: { type: String },
preguntas: [
{
type: Schema.Types.ObjectId,
ref: 'Pregunta',
},
],
});
PREGUNTA MODEL
const Schema = mongoose.Schema;
const preguntaSchema = new Schema({
titulo: { type: String, required: true },
});
A "Seccion" document with 2 "preguntas" looks something like this:
{
"preguntas": [
"5fb0a48ccb68c44c227a0432",
"5fb0a48ccb68c44c227a0433"
],
"_id": "5fb0a50fcb68c44c227a0436",
"titulo": "Seccion 2",
"__v": 3
}
What I want to do is change the order in the preguntas array, so that the _ids end up being [33, 32] instead of [32, 33] (last two digits of the _ids). I can update using ".save()" method or ".findOneAndUpdate()". Here is the code using "findOneAndUpdate":
router.put('/:id', async (req, res) => {
const seccionId = req.params.id;
try {
const seccion = await Seccion.findOneAndUpdate(
{ _id: seccionId },
{ preguntas: ['5fb0a48ccb68c44c227a0433', '5fb0a48ccb68c44c227a0432'] },
{ new: true }
);
res.json(seccion);
} catch (err) {
res.json({ message: err });
}
});
The result works (and the order is changed in mongo atlas):
{
"preguntas": [
"5fb0a48ccb68c44c227a0433",
"5fb0a48ccb68c44c227a0432"
],
"_id": "5fb0a50fcb68c44c227a0436",
"titulo": "Seccion 2",
"__v": 4
}
But when I try to use the "populate()" method of mongoose, only one of the "preguntas ids" or none are populated (below only the "pregunta" with id ending in 32 was populated):
{
"preguntas": [
{
"_id": "5fb0a48ccb68c44c227a0432",
"titulo": "Pregunta 3",
"__v": 0
}
],
"_id": "5fb0a50fcb68c44c227a0436",
"titulo": "Seccion 2",
"__v": 4
}
So my guess is that the populate method is not working correctly, but it does work on the other "Seccion" documents where I have not tryed to change the order of the "preguntas" inside the array. Here is the code that uses the "populate" method:
router.get('/:id', async (req, res) => {
const seccionId = req.params.id;
try {
const seccion = await Seccion.findById(seccionId).populate('preguntas');
res.json(seccion);
} catch (err) {
res.json({ message: err });
}
});
It has been really hard to make the populate work, I really appreciate anyone's help
Are you sure that pregunta of id 5fb0a48ccb68c44c227a0433 exists in the preguntas collection?
The way populate works is that it does a second find operation on the preguntas collection with the ids in the array, if a document is not found, it will be omitted from the array.
The issue is probably that the one ending with 33 does not exist in the preguntas collection.

How do i convert the output of Mongoose.find

I have structure like this
adminID: { type: String, default: "" },
consumableCats: [{
consumableCatName: { type: String, default: "" },
.....
consumables: [{
name: { type: String, default: "" },
....
_id:....
}]
}]
}],
I make a mongoose.find() and i exclude all fields inside consumables except for id
Store.findOne({
'_id': req.params.id
}, {'consumableCats.consumables.name': 0,
...
'consumableCats.consumables.preparationLocation': 0,
'__v': 0
});
and i get this:
"consumables": [
{
"_id": "5d1d114314f9ae439965904d"
},
{
"_id": "5d1de09fce3d033fb0b50bcf"
},
..
]
when i want to get just an array of IDs like
"consumables": [
"T7LmaegQO22sE6fB7L8hmLPGrhKwxNnb",
"lSYZTp44_yPVnXeZXr3NaaWi_m2PgsIS",
.....
]
any ideas?
First you store the returned array in a variable called consumables.
let consumables = [ //you get this array from mongoose.find()
{
"_id": "5d1d114314f9ae439965904d"
},
{
"_id": "5d1de09fce3d033fb0b50bcf"
}
]
You can create a new Empty array let resultArray = []
Then you can iterate over each element in the consumables array of objects and push it to the result array like this:
consumables.forEach(val=>{resultArray.push(val._id)});
console.log(resultArray); // returns ["5d1d114314f9ae439965904d","5d1de09fce3d033fb0b50bcf"]
Hope this has solved your issue.

Mongoose query on subdocument returns array of other subdocument using projection

Update: I am looking for an answer that works within mongodb projection: https://docs.mongodb.com/manual/reference/method/db.collection.findOne/#definition
I am trying to filter a query on a subdocument using projection so that it only returns a specific array. But when filtering the result also includes an array of another subdocument. When I don't filter it only returns the found document.
I tried different filtering options including and excluding positional elements, but can't get the desired return.
Mongoose schema
const stationSchema = new mongoose.Schema({
mac: String,
stationName: String,
syncReadings: Boolean,
temperature: Array,
humidity: Array,
measures: [{
date: Date,
temperature: Number,
humidity: Number
}],
lastUpdated: Date
});
// Define user schema
var userSchema = mongoose.Schema({
local : {
email : String,
password : String
},
facebook : {
id : String,
token : String,
name : String,
email : String
},
twitter : {
id : String,
token : String,
displayName : String,
username : String
},
google : {
id : String,
token : String,
email : String,
name : String
},
apiKey: String,
stations : [stationSchema]
},
{
usePushEach: true
}
);
Api handler
app.get('/api/stations/:stationName/measures', function(req, res, next) {
var user = {
apiKey: req.user.apiKey
}
const query = {
apiKey: user.apiKey,
stations.stationName': req.params.stationName
}
const options = {
'stations.measures': 1
}
User.findOne(query, options)
.exec()
.then(stations => {
res.status(200).send(stations)
})
.catch(err => {
console.log(err);
res.status(400).send(err);
})
});
There are two stations under one user:
[
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"mac": "5C:CF:7F:77:12:FB",
"_id": "5c39c9ab56bbf002fb092cea",
"lastUpdated": "2019-01-12T11:07:01.802Z",
"syncReadings": false,
"measures": [],
"humidity": [],
"temperature": [
{
"date": "2019-01-12T11:07:01.802Z",
"temperature": "20"
}
]
},
{
"stationName": "office",
"mac": "5C:CF:7F:77:12:FC",
"_id": "5c39cacdce4ac903123f0150",
"measures": [],
"humidity": [],
"temperature": []
}
]
}
]
API call
http://localhost:8080/api/stations/livingroom/measures
Result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"measures": []
},
{
"measures": []
}
]
}
Projection options tried
const options = {
'stations.measures': 1
}
const options = {
'stations.$.measures': 1
}
const options = {
'stations.$': 1,
'stations.$.measures': 1
}
const options = {
'stations.$': 1,
'stations.measures': 1
}
What am I doing wrong?
try using these querying params only and after that you will get a user with a requested station..
var user = {
apiKey: req.user.apiKey
}
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
then do this
User.findOne(query, options)
.exec()
.then(stations => {
for(let station of stations){
if(station.measures[1]){ // here it is the index
res.status(200).send(stations);
}
}
})
.catch(err => {
console.log(err);
res.status(400).send(err);
})
actually in mongoose you cannot query sub-sub documents to you will have to you this approach.. You can only query sub docs only like you have done