I am trying to read a single field in a firestore document, increment the field by one and update the field along side two other fields in the document.
It seems firebase transaction update() function accept a JSON object with only one field and value because when I add other fields to the JSON, the update fails.
This works:
t.update(referralCodesDocRef, {field1: value1});
This does not work:
t.update(referralCodesDocRef, {
field1: value1,
field2: value2,
field3: value3
});
Also this does not work:
t.update(referralCodesDocRef, {field1: value1});
t.update(referralCodesDocRef, {field2: value2});
t.update(referralCodesDocRef, {field3: value3});
Here is the function that does the transaction
function runIncreaseCountTransaction(referralCodesDocRef){
return db.runTransaction(t => {
return t.get(referralCodesDocRef)
.then(doc => {
console.log(doc);
let newReferralCount = doc.data().referral_count + 1;
if(newReferralCount === max_referral_count){
const current_time_millis = Date.now();
const end_time_millis = current_time_millis+(180*1000); // ends after 3 mins
t.update(referralCodesDocRef, {referral_count: newReferralCount});
t.update(referralCodesDocRef, { timer_start_time: current_time_millis });
t.update(referralCodesDocRef, { timer_end_time: end_time_millis });
}
else{
t.update(referralCodesDocRef, { referral_count: newReferralCount });
}
return Promise.resolve(newReferralCount);
})
.then(result => {
console.log('Success: Update successful: Referral count incremented!!', result);
return true;
}).catch(err => {
console.log('Error: could not update referral count', err);
});
});
}
So how can I achieve multiple fields update with firebase transactions?
None of the answers are correct (at least didn't work for me).
Kotlin
Here is my implementation. Very easy:
val db = Firebase.firestore
db.collection("Users")
.document("Ronaldo")
.update("famous", true,
"distance", 5)
.addOnSuccessListener {...
.addOnFailureListener {...
So basically add another comma after your first pair
There should be no problem at all to update a document with a JavaScript object composed of several properties, like
t.update(referralCodesDocRef, {
field1: value1,
field2: value2,
field3: value3
});
The problem most probably comes from the fact that you don't return the Transaction returned by the Transaction's update() method. The following should do the trick:
function runIncreaseCountTransaction(referralCodesDocRef){
return db.runTransaction(t => {
return t.get(referralCodesDocRef)
.then(doc => {
console.log(doc);
let newReferralCount = doc.data().referral_count + 1;
if (newReferralCount === max_referral_count) {
const current_time_millis = Date.now();
const end_time_millis = current_time_millis+(180*1000); // ends after 3 mins
return t.update(referralCodesDocRef,
{
referral_count: newReferralCount,
timer_start_time: current_time_millis,
timer_end_time: end_time_millis
});
} else{
return t.update(referralCodesDocRef, { referral_count: newReferralCount });
}
});
})
.then(result => {
console.log('Success: Update successful: Referral count incremented!!', result);
return null;
})
.catch(err => {
console.log('Error: could not update referral count', err);
return null; //Note the return here.
});
}
Kotlin
You can update multiple fields in a Firestore document using the field, value, field, value... format:
db.collection("users").doc("docID").update({
"field1", value1, // field, value
"field1, "value2" // field, value
})
If you want to update a nested field, you have a couple options.
Use the dot notation like you mentioned:
db.collection("users").doc("docID").update({
"field1", value1,
"field2.subfield2", value2,
"field2.subfield3", value3
})
Make your "value" a map:
db.collection("users").doc("docID").update({
"field1", value1,
"field2", mapOf(
"field3" to value3,
"field4" to value4
})
Related
I was trying to pull only one element from an Array which is a mongo document.
My db looks like this:
product: ['foo','foo','foo'],
...
I want to remove only one element from this array.
My db should Look like this:
product: ['foo','foo'],
...
When I was using model.findByIdAndUpdate( id, { $pull: {product: 'foo'} } )
I loose all my values from product
`
doc.findById(productId)
.then(async (result) => {
if (!result) {
console.log("No Record Found.");
} else {
result.index = undefined;
await result.save();
console.log('Record Updated Successfully.');
}
})
.catrch(err => {
console.log('Error: ', err);
});
`
You can do this to bring the document and then make the required element as undefined and then you can save the modified document back to the database.
Else you can have a better and optimized approach.
`db.doc.update( { _id: 1 }, { $pop: { products: -1 } } )`
Here the first element from the product array will be deleted.
Note: this is the MongoDB shell syntax.
I have my data like this:
{"_id":"5c0a17013d8ca91bf4ee7885",
"ep1":{
"email":"test#gmail.com",
"password":"123456",
"phoneNo":"123456789",
"endpointId":"ep1"
}
}
I want to match all the object that has key ep1 and value "email":"test#gmail.com" and set ep1:{}
Code:
My req object is like this {"endpointId":"ep1", "email":
"test#gmail.com"}
let query = {[req.body.endpointId]:{email: req.body.email}};
endpointModelData.endpoint.updateMany(query, {[req.body.endpointId]:{}}, {multi: true}, function(err, doc){
if (err) return res.send(500, { error: err });
console.log(doc)
return res.status(201).send("success.");
})
But this code only works if there is only one entry(that is email) inside my ep1 object
like this:
{"_id":"5c0a17013d8ca91bf4ee7885",
"ep1":{
"email":"test#gmail.com"
}
}
So, how can I match email and ignore other fields?
You need to match: {"ep1.email":"test#gmail.com"} so change your query to:
let query = { [`${req.body.endpointId}.email`]: req.body.email };
You can see that filter working here
On the end of the day your update should look like:
updateMany({ "ep1.email" : "test#gmail.com" }, { "ep1": {} }, {multi: true})
I am trying to build an app involves posts and tags for posts. For these I have a post, tags and post_tag table. tags has the tags I have defined before hand and in somewhere in the app is suggested to the user on the front-end. post_tag table holds the post and tag ids as pairs on each row.
I use express.js and postgreql and pg-promise.
As far as I know I need a transactional query(ies) for a create post operation.
Also I need a mechanism to detect if a tag was not in tags table when the user created the post, so that I can insert it on the fly, and I have a tag_id for each tag that is neccessary to use in insertion of the post_id and tag_id into post_tag table. Otherwise, I will have a foreign key error since I need to post_tag table's columns post_id and tag_id to reference posts and tags table id columns, respectively.
Here is the url function I use for this I have used so far unsuccessful:
privateAPIRoutes.post('/ask', function (req, res) {
console.log('/ask req.body: ', req.body);
// write to posts
var post_id = ''
var post_url = ''
db.query(
`
INSERT INTO
posts (title, text, post_url, author_id, post_type)
VALUES
($(title), $(text), $(post_url), $(author_id), $(post_type))
RETURNING id
`,
{
title: req.body.title,
text: req.body.text,
post_url: slug(req.body.title),
author_id: req.user.id,
post_type: 'question'
} // remember req.user contains decoded jwt saved by mw above.
)
.then(post => {
console.log('/ask post: ', post);
post_id = post.id
post_url = post.post_url
// if tag deos not exist create it here
var tags = req.body.tags;
console.log('2nd block tags1', tags);
for (var i = 0; i < tags.length; i++) {
if (tags[i].id == undefined) {
console.log('req.body.tags[i].id == undefined', tags[i].id);
var q1 = db.query("insert into tags (tag) values ($(tag)) returning id", {tag: tags[i].label})
.then(data => {
console.log('2nd block tags2', tags);
tags[i].id = data[0].id
// write to the post_tag
db.tx(t => {
var queries = [];
for (var j = 0; j < tags.length; j++) {
var query = t.query(
`
INSERT INTO
post_tag (post_id, tag_id)
VALUES
($(post_id), $(tag_id))
`,
{
post_id: post_id,
tag_id: tags[j].id
}
)
queries.push(query);
}
return t.batch(queries)
})
.then(data => {
res.json({post_id: post_id, post_url: post_url})
})
.catch(error => {
console.error(error);
})
})
.catch(error => {
console.error(error);
});
}
}
})
.catch(error => {
console.error(error);
})
});
The main problem you have - you can't use the root-level db object inside a task or transaction. Trying to create a new connection while inside a transaction breaks the transaction logic. You would need to use t.tx in such cases. However, in your case I don't see that you need it at all.
corrected code:
privateAPIRoutes.post('/ask', (req, res) => {
console.log('/ask req.body: ', req.body);
db.tx(t => {
return t.one(
`
INSERT INTO
posts (title, text, post_url, author_id, post_type)
VALUES
($(title), $(text), $(post_url), $(author_id), $(post_type))
RETURNING *
`,
{
title: req.body.title,
text: req.body.text,
post_url: slug(req.body.title),
author_id: req.user.id,
post_type: 'question'
} // remember req.user contains decoded jwt saved by mw above.
)
.then(post => {
console.log('/ask second query: post[0]: ', post);
console.log('/ask second query: tags: ', req.body.tags);
console.log('/ask second query: tags[0]: ', req.body.tags[0]);
// the key piece to the answer:
var tagIds = req.body.tags.map(tag => {
return tag.id || t.one("insert into tags(tag) values($1) returning id", tag.label, a=>a.id);
});
return t.batch(tagIds)
.then(ids => {
var queries = ids.map(id => {
return t.one(
`
INSERT INTO post_tag (post_id, tag_id)
VALUES ($(post_id), $(tag_id))
RETURNING post_id, tag_id
`,
{
post_id: post.id,
tag_id: id
}
)
});
return t.batch(queries);
});
});
})
.then(data => {
// data = result from the last query;
console.log('/api/ask', data);
res.json(data);
})
.catch(error => {
// error
});
});
The key here is simply to iterate through the tag id-s, and for the ones that are not set - use an insert. Then you settle them all by passing the array into t.batch.
Other recommendations:
You should use method one when executing an insert that returns the new record columns.
You should use try/catch only once there, on the transaction. This is relevant to how to use promises, and not just for this library
You can place your queries into external SQL files, see Query Files
To understand conditional inserts better, see SELECT->INSERT
I wanted to delete a document with concerned _id and email when I click on "Remove task" in the HTML file.
Following is the code which removes that task:
I've passed value of email and _id(only hexadcemial string value) to the code:
collection.findOneAndDelete({email:email,_id:taskid},function (err, result) {
if (err) {
console.log(err);
} else {
console.log("Removed!");
console.log(result);
callback(result);
}
db.close();
});
But, the function is not recognizing _id that I've passed. The value of "taskid" variable is 566836bd8db43d3d56e23a4a i.e. only strings value from _id:
ObjectId("566836bd8db43d3d56e23a4a")
var taskid=566836bd8db43d3d56e23a4a;
I've tried every possible declaration of taskid to convert it so that the function could recognize the value of _id and match it:
var taskid= "ObjectId("+'"'+req.param('taskid')+'"'+")";
But till now, I am not able to match the _id with the taskid. Any fix?
if you are going to compare with ObjectId then
var ObjectId = require('mongoose').Types.ObjectId
collection.findOneAndDelete({email:email,_id:new ObjectId(taskid)},function (err, result) {
if (err) {
console.log(err);
} else {
console.log("Removed!");
console.log(result);
callback(result);
}
db.close();
});
Should work for you.
If you feel the job too hard for each and every query then you can create an new method.
String.prototype.toObjectId = function() {
var ObjectId = (require('mongoose').Types.ObjectId);
return new ObjectId(this.toString());
};
// Every String can be casted in ObjectId now
console.log('545f489dea12346454ae793b'.toObjectId());
I would like to retrieve some data from a Mongoose setting in my Node.js application. I noticed that no matter what I write as field selection, I always get the _id field. Is there a way not to fetch it?
This is how I do right now:
Transaction.find({username : user.username}, ['uniqueId', 'timeout', 'confirmation_link', 'item_name'], function(err, txs){
console.log("user : " + user.username + " with txs: " + txs);
callback(txs);
});
And logs me the results which contain the _id field.
Another way is to use text argument with prefix - which will exclude this or that field from the result:
Entity.find({ ... }, '-_id field1 field2', function(err, entity) {
console.log(entity); // { field1: '...', field2: '...' }
});
_id must be specifically excluded. For example,
Transaction.find({username : user.username}, { '_id': 0, 'uniqueId' :1, 'timeout': 1, 'confirmation_link': 1, 'item_name': 1}, function(err, txs){
console.log("user : " + user.username + " with txs: " + txs);
callback(txs);
});
Another approach:
Augment the .toJSON() of the schema that it deletes the _id and the __v fields
Call .toJSON() on all DB objects sent to client
Extra benefit #1: you can use item.id === 'something' because typeof id === 'string', not ObjectId.
Extra benefit #2: When you got gan object back from the client and you want to search / update then you don't have to manually delete _id because there is none, just an id which is ignored.
Augmenting JSON:
mySchema.set('toJSON', {
virtuals: true,
transform: (doc, ret, options) => {
delete ret.__v;
ret.id = ret._id.toString();
delete ret._id;
},
});
So you can use:
let item = (await MyCollection.findOne({/* search */}).exec()).toJSON();
if (item.id === 'someString') return item;
I know it's ugly. But it's the best bad idea that I have so far.
In 5.2.13 version of Mongoose (Sept 2018)- using the query builder approach the same can be converted to
async function getUserDetails(user) {
try {
if (!user || !user.name) return;
const result = await Transaction.
find({username : user.username}).
select('uniqueId timeout confirmation_link item_name -_id');
// Adding minus sign before the _id (like -_id) in the select string unselects the _id which is sent by default.
console.log(result);
} catch(ex) {
return ex
}
}
The easiest thing you can do is something like this:
Transaction.find({username : user.username}, {_id: 0}, (err, txs) => {
// the return document won't contain _id field
// callback function body
}
Just remember that in the second object passed in the find()-
Pass 0 as the value to the specific key that you wish not to fetch
from the mongodb database.
Pass 1 as the value when you wish to
fetch from the mongodb database.