Here is a javascript function intending to perform an update on FireStore, which does not work.
I will be more that happy if anyone can see an issue in the code.
function makeUpdate(key,name) {
let theCollection = db.collection("InformationList"),
infoUnit = theCollection.doc(key).get().then(function(doc) {
if (doc.exists) {
console.log("infoUnit -name-:" + doc.get("name"));
console.log("infoUnit -telephone-:" + doc.get("telephone"));
let updateDico = {};
updateDico["name"] = name;
doc.update(updateDico);
} else {
console.log("embassyUpdate --> No such document!");
}
}).catch(err => {
console.log("Error getting documents (in makeUpdate)", err);
});
}
Apart from the fact that it does not perform the expected update, it prints three messages in the logs:
infoUnit -name-: some name
infoUnit -telephone-: some telephone number
Error getting documents (in makeUpdate)
From that I can see that a record is found in the database as expected. But at the same time an unknown error occurs.
There is no update() method on doc (which a DocumentSnapshot object). A DocumentSnapshot just contains the data read from get(). If you want to write data back into a document, you'll need to use a DocumentReference object, probably the same one you got when you called theCollection.doc(key).
There is no such method called update() which you can invoke on doc DataSnapshot object itself.
You'll have to use the set() method on the Document Reference which you get from doc.ref to update the reference.
This is how I've updated my data.
await db
.collection('collectionName')
.doc('documentId')
.update({
name: "Updated Name",
telephone: "0000000000"
});
You need to know document id and you can update your value like this.
Related
I am building a chatbot on Dialogflow and using Firestore as my database. I was originally using a custom auto ID generator, but I am now trying to use Firestores in built one, and getting errors with my current code.
function AddWaterConsumption (agent) {
// Get parameter from Dialogflow with the string to add to the database
var databaseEntry = {
"PatientID": agent.parameters.Patient_ID,
"DailyConsumption": agent.parameters.Water_consumption_user,
"DailyWaterPlan": agent.parameters.Daily_water_plan,
};
let Patient_ID = agent.parameters.Patient_ID;
console.log(`Okay, we're trying to write to database: ${databaseEntry}`);
// here is where changes are needed
const dialogflowAgentRef = db.collection("WaterConsumption").doc(genRand(8)); // need to change to add()
console.log(`Found agent ref: ${dialogflowAgentRef}`);
return db.runTransaction(t => {
t.set(dialogflowAgentRef, databaseEntry); // this will also need to be changed
return Promise.resolve('Write complete');
}).then(doc => {
agent.add(`I have updated your daily water consumption`);
agent.add(`Is anything else I can do for you?.`);
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${dialogflowAgentRef}" to the Firestore database.`);
});
}
I have changed the important lines to:
console.log(`Okay, we're trying to write to database: ${databaseEntry}`);
const dialogflowAgentRef = db.collection("WaterConsumption"); // changed here
console.log(`Found agent ref: ${dialogflowAgentRef}`);
return db.runTransaction(t => {
t.set(db.collection("WaterConsumption").add(databaseEntry), databaseEntry); // and here
Which does successfully write to the db and auto generates an ID. Although an error is being caught:
Argument "documentRef" is not a valid DocumentReference. Input is not a plain JavaScript object
This makes no sense:
t.set(db.collection("WaterConsumption").add(databaseEntry), databaseEntry);
In this line the db.collection("WaterConsumption").add(databaseEntry) is a call to Firestore to create a document outside of the transaction). What you want instead is to just create a new DocumentReference with a unique ID, which (as shown in the 3rd snippet in the documentation on adding documents) can be done with doc().
So:
const newDocRef = db.collection("WaterConsumption").doc();
t.set(newDocRef, databaseEntry);
In a DocDb stored procedure, as the first step in a process retrieving data that I'm mutating, I read and then use the data iff it matches the etag like so:
collection.readDocument(reqSelf, function(err, doc) {
if (doc._etag == requestEtag) {
// Success - want to update
} else {
// CURRENTLY: Discard the read result I just paid lots of RUs to read
// IDEALLY: check whether response `options` or similar indicates retrieval
was skipped due to doc not being present with that etag anymore
...
// ... Continue with an alternate strategy
}
});
Is there a way to pass an options to the readDocument call such that the callback will be informed "It's changed so we didn't get it, as you requested" ?
(My real problem here is that I can't find any documentation other than the readDocument undocumentation in the js-server docs)
Technically you can do that by creating a responseOptions object and passing it to the call.
function sample(selfLink, requestEtag) {
var collection = getContext().getCollection();
var responseOptions = { accessCondition: { type: "IfMatch", condition: requestEtag } };
var isAccepted = collection.readDocument(selfLink, responseOptions, function(err, doc, options) {
if(err){
throw new Error('Error thrown. Check the status code for PreconditionFailed errors');
}
var response = getContext().getResponse();
response.setBody(doc);
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
However, even if the etag you provide is not the one that the document has, you won't get an error and you will properly get the document itself back. It's just not supposed to work with that using the readDocument function in a stored procedure.
Thanks to some pushing from #Nick Chapsas, and this self-answer from #Redman I worked out that in my case I can achieve my goal (either read the current document via the self-link, or the newer one that has replaced it bearing the same id) by instead generating an Alt link within the stored procedure like so:
var docId = collection.getAltLink() + "/docs/"+req.id;
var isAccepted = collection.readDocument(docId, {}, function (err, doc, options) {
if (err) throw err;
// Will be null or not depending on whether it exists
executeUpsert(doc);
});
if (!isAccepted) throw new Error("readDocument not Accepted");
I am attempting to perform an update to a MongoDB document (using mongoose) by first using .findById to get the document, then updating the fields in that document with new values. I am still a bit new to this so I used a tutorial to figure out how to get it working, then I have been updating my code for my needs. Here is the tutorial: MEAN App Tutorial with Angular 4. The original code had a schema defined, but my requirement is for a generic MongoDB interface that will simply take whatever payload is sent to it and send it along to MongoDB. The original tutorial had something like this:
exports.updateTodo = async function(todo){
var id = todo.id
try{
//Find the old Todo Object by the Id
var oldTodo = await ToDo.findById(id);
}catch(e){
throw Error("Error occured while Finding the Todo")
}
// If no old Todo Object exists return false
if(!oldTodo){
return false;
}
console.log(oldTodo)
//Edit the Todo Object
oldTodo.title = todo.title
oldTodo.description = todo.description
oldTodo.status = todo.status
console.log(oldTodo)
try{
var savedTodo = await oldTodo.save()
return savedTodo;
}catch(e){
throw Error("And Error occured while updating the Todo");
}
}
However, since I don't want a schema and want to allow anything through, I don't want to assign static values to specific field names like, title, description, status, etc. So, I came up with this:
exports.updateData = async function(update){
var id = update.id
// Check the existence of the query parameters, If they don't exist then assign a default value
var dbName = update.dbName ? update.dbName : 'test'
var collection = update.collection ? update.collection : 'testing';
const Test = mongoose.model(dbName, TestSchema, collection);
try{
//Find the existing Test object by the Id
var existingData = await Test.findById(id);
}catch(e){
throw Error("Error occurred while finding the Test document - " + e)
}
// If no existing Test object exists return false
if(!existingData){
return false;
}
console.log("Existing document is " + existingData)
//Edit the Test object
existingData = JSON.parse(JSON.stringify(update))
//This was another way to overwrite existing field values, but
//performs a "shallow copy" so it's not desireable
//existingData = Object.assign({}, existingData, update)
//existingData.title = update.title
//existingData.description = update.description
//existingData.status = update.status
console.log("New data is " + existingData)
try{
var savedOutput = await existingData.save()
return savedOutput;
}catch(e){
throw Error("An error occurred while updating the Test document - " + e);
}
}
My original problem with this was that I had a lot of issues getting the new values to overwrite the old ones. Now that that's been solved, I am getting the error of "TypeError: existingData.save is not a function". I am thinking the data type changed or something, and now it is not being accepted. When I uncomment the static values that were in the old tutorial code, it works. This is further supported by my console logging before and after I join the objects, because the first one prints the actual data and the second one prints [object Object]. However, I can't seem to figure out what it's expecting. Any help would be greatly appreciated.
EDIT: I figured it out. Apparently Mongoose has its own data type of "Model" which gets changed if you do anything crazy to the underlying data by using things like JSON.stringify. I used Object.prototype.constructor to figure out the actual object type like so:
console.log("THIS IS BEFORE: " + existingData.constructor);
existingData = JSON.parse(JSON.stringify(update));
console.log("THIS IS AFTER: " + existingData.constructor);
And I got this:
THIS IS BEFORE: function model(doc, fields, skipId) {
model.hooks.execPreSync('createModel', doc);
if (!(this instanceof model)) {
return new model(doc, fields, skipId);
}
Model.call(this, doc, fields, skipId);
}
THIS IS AFTER: function Object() { [native code] }
Which showed me what was actually going on. I added this to fix it:
existingData = new Test(JSON.parse(JSON.stringify(update)));
On a related note, I should probably just use the native MongoDB driver at this point, but it's working, so I'll just put it on my to do list for now.
You've now found a solution but I would suggest using the MongoDB driver which would make your code look something along the lines of this and would make the origional issue disappear:
// MongoDB Settings
const MongoClient = require(`mongodb`).MongoClient;
const mongodb_uri = `mongodb+srv://${REPLACE_mongodb_username}:${REPLACE_mongodb_password}#url-here.gcp.mongodb.net/test`;
const db_name = `test`;
let db; // allows us to reuse the database connection once it is opened
// Open MongoDB Connection
const open_database_connection = async () => {
try {
client = await MongoClient.connect(mongodb_uri);
} catch (err) { throw new Error(err); }
db = client.db(db_name);
};
exports.updateData = async update => {
// open database connection if it isn't already open
try {
if (!db) await open_database_connection();
} catch (err) { throw new Error(err); }
// update document
let savedOutput;
try {
savedOutput = await db.collection(`testing`).updateOne( // .save() is being depreciated
{ // filter
_id: update.id // the '_id' might need to be 'id' depending on how you have set your collection up, usually it is '_id'
},
$set: { // I've assumed that you are overwriting the fields you are updating hence the '$set' operator
update // update here - this is assuming that the update object only contains fields that should be updated
}
// If you want to add a new document if the id isn't found add the below line
// ,{ upsert: true }
);
} catch (err) { throw new Error(`An error occurred while updating the Test document - ${err}`); }
if (savedOutput.matchedCount !== 1) return false; // if you add in '{ upsert: true }' above, then remove this line as it will create a new document
return savedOutput;
}
The collection testing would need to be created before this code but this is only a one-time thing and is very easy - if you are using MongoDB Atlas then you can use MongoDB Compass / go in your online admin to create the collection without a single line of code...
As far as I can see you should need to duplicate the update object. The above reduces the database calls from 2 to one and allows you to reuse the database connection, potentially anywhere else in the application which would help to speed things up. Also don't store your MongoDB credentials directly in the code.
Here is my data:
I want to iterate through each event_prod in event_prods and go to the eventGroups subcollection. Once in that sub-collection, I want to loop through each eventGroup in eventGroups and get doc data.
Here's my code thus far:
async function getAllEventGroups() {
let eventGroups = []
try {
let eventProducerRef = await db.collection('event_prods')
let allEventProducers = eventProducerRef.get().then(
producer => {
producer.forEach(doc => console.log(doc.collection('eventGroups'))
}
)
} catch (error) {
console.log(`get(): there be an error ${error}`)
return []
}
return eventGroups
}
Obviously, it doesn't do what I want, but I can't figure out how to get access to the eventGroups subcollection. Calling 'collection()' on 'doc' is undefined. Can someone please help fix this? By the way, I don't care if this requires two (or more) queries as long as I don't have to bring in data I will never use.
Edit: this is not a duplicated because I know the name of my subcollection
You call the .collection on the QueryDocumentSnapshot. This methods doesn't exist there. But as the QueryDocumentSnapshot extends DocumentSnapshot you can call ref on it to get the reference to the requested document.
```
let allEventProducers = eventProducerRef.get().then(
producer => {
producer.forEach(doc => console.log(doc.ref.collection('eventGroups')) // not the ref here
}
)
eventProducerRef is a CollectionReference. The get() method on that yields a QuerySnapshot, which you are storing in producer. When you iterate it with forEach(), you are getting a series of QueryDocumentSnapshot objects, which you're storing in doc. QueryDocumentSnapshot doesn't have a method called collection(), as you are trying to use right now.
If you want to reach into a subcollection of a document, build a DocumentReference to the document, then call its collection() method. You'll need to use the id of each document for this. Since a QueryDocumentSnapshot subclasses DocumentSnapshot, you can use its id property for this:
let eventProducerRef = await db.collection('event_prods')
let allEventProducers = eventProducerRef.get().then(
producer => {
producer.forEach(snapshot => {
const docRef = eventProducerRef.doc(snapshot.id)
const subcollection = docRef.collection('eventGroups')
})
}
)
I am trying to make two function.
Save() should check if there is an existing document for that user, and if there is then update his save with a new one, and if there is not then insert a new doc using the user's unique id as the docs unique id.
Load() should check if there is an existing save with the user's Id and load it.
I am purely new to that and here is the error I get
Uncaught Error: Not permitted. Untrusted code may only update
documents by ID. [403]
I get that it happens because of how update and insert work. But I want to use the user's unique iD for documents, because it looks simple.
function Save() {
if (Meteor.userId()) {
player = Session.get("Player");
var save = {
id: Meteor.userId(),
data = "data"
};
console.log(JSON.stringify(save));
if (Saves.find({id: Meteor.userId()})){
Saves.update( {id: Meteor.userId()}, {save: save} )
console.log("Updated saves")
}
else {
Saves.insert(save)
}
console.log("Saved");
}
}
function Load(){
if (Meteor.userId()){
if (Saves.find(Meteor.userId())){
console.log(JSON.stringify(Saves.find(Meteor.userId()).save.player));
player = Saves.find(Meteor.userId()).save.player;
data= Saves.find(Meteor.userId()).save.data
}
}
}
Objects/documents id-field is called _id.
See here!
The error occurs when you try the update of the existing object/document on the client side.
You always need to pass in the objects _id to update the object/document from client code.
Note that you always try to pass an id not an _id!
So try it like this:
function Save() {
if (Meteor.userId()) {
player = Session.get("Player");
var save = {
_id: Meteor.userId(),
data = "data"
};
console.log(JSON.stringify(save));
if (Saves.find({_id: Meteor.userId()})){
Saves.update( {_id: Meteor.userId()}, {save: save} )
console.log("Updated saves")
}
else {
Saves.insert(save)
}
console.log("Saved");
}
}
Also note that your Load() function could work, because Collection.find() uses the string you pass as an _id for the document.
Hope that helped!