How to automatically update same field in different collections in Firestore - firebase

I am using Cloud Firestore as my database and I have collections of users where are stored basic information about user such as id, name, last name, email, company id.
Also I have collection of companies and in each company I have collection of tasks.
In each task I have one user assigned from collections of users (user data is replicated, so I have same data for that user as in collection users)
The problem is when I update user (change name or email...) from collection users because data is replicated that data is not changed in collection of tasks for that specific user.
Is there any way that using firestore when user from collection users is updated to automatically update it in collection of tasks?

This is quite a standard case in NoSQL databases, where we often denormalize data and need to keep these data in sync.
Basically you have two possible main approaches:
#1 Update from the client
When you update the "user" document, update at the same time the other documents (i.e. "tasks") which contain the user's details. You should use a batched write to do so: A batch of writes completes atomically and can write to multiple documents.
Something along the following lines:
// Get a new write batch
var batch = db.batch();
var userRef = db.collection('users').doc('...');
batch.update(userRef, {name: '....', foo: '....'});
let userTaskRef = db.collection('companies').doc('...').collection('tasks').doc('taskId1');
batch.update(userTaskRef, {name: '....'});
userTaskRef = db.collection('companies').doc('...').collection('tasks').doc('taskId2');
batch.update(userTaskRef, {name: '....'});
// ...
// Commit the batch
batch.commit().then(function () {
// ...
});
Note that you need to know which are "the other ("tasks") documents which contain the user's details": you may need to do a query to get these documents (and their DocumentReferences).
#2 Update in the back-end via a Cloud Function
Write and deploy a Cloud Function that is triggered when any "user" document is updated and which takes the value of this "user" document and update the "tasks" documents which contain the user's details.
Like for the first approach, you also need, in this case, to know which are "the other ("tasks") documents which contain the user's details.
Following your comment ("Is there any option to reference to another table or put foreign key?") here is a Cloud Function that will update all the ("tasks") documents that have their DocumentReference contained in a dedicated Array field taskRefs in the "user" doc. The Array members are of data type Reference.
exports.updateUser = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
const newValue = change.after.data();
const name = newValue.name;
const taskRefs = newValue.taskRefs;
const promises = taskRefs.map(ref => { ref.update({ name: name, foo: "bar" }) });
return Promise.all(promises);
});
You would most probably set the value of this taskRefs field in the "user" doc from your frontend. Something along the following lines with the JS SDK:
const db = firebase.firestore();
db.collection('users').doc('...').set({
field1: "foo",
field2: "bar",
taskRefs: [ // < = This is an Array of References
db.collection('tasks').doc('....'),
db.collection('tasks').doc('....')]
});

Related

Flutter web with Firestore: Set in every document a field to a value

I want to set in every document of a collection, a specific field to 0. How can I do this?
To do in dart:
Geting all documents and update fields:
QuerySnapshot snapshot = await FirebaseFirestore.instance
.collection("collectionName").doc("documentName").get();
snapshot.docs.forEach((doc) {
FirebaseFirestore.instance.collection("collectionName").doc(doc.id).update({
"fieldName": 0,
});
});
But I recommend using the firebase admin for this type of activity, because if there are many documents you can exceed the limits of its use in Firestore, stay tuned! With firebase admin you can use different types of languages, with python for example, and update everything at once (Batched writes), in a single activity.
Example in Python (Firebase admin):
reference = db.collection(u'themes')
query_snapshot = reference.where(u'collections', u'array_contains', u'reflections').stream()
batch = db.batch()
for doc in query_snapshot:
doc_data = doc.to_dict()
new_data = {}
new_data["keywords_pt"] = generate_keywords(doc_data[u"title-pt"].lower())
new_data["keywords_es"] = generate_keywords(doc_data[u"title-es"].lower())
new_data["keywords_en"] = generate_keywords(doc_data[u"title-en"].lower())
batch.update(reference.document(doc.id), new_data)
batch.commit()
For more information:
Firebase admin: https://firebase.google.com/docs/database/admin/start
Batched writes: https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes

Determine RTDB url in a trigger function

i m bulding a scalable chat app with RTDB and firestore
here is my raw structure of shards
SHARD1
Chats {
chat01: {
Info: {
// some info about this chatroom
},
Messages ...
}, ....
}
SHARD2...
now i have write triggers on all the info nodes of all the shards.
i want get the ID of the shard
How do i know what shard it actually ran on ?
[EDIT]
console.log(admin.app().name); // it prints "[DEFAULT]" in console
Puf and team please help
When a Realtime Database trigger is invoked, the second argument is an EventContext object that contains information about the database and node that was updated. That object contains a resource string, which has what you're looking for. According to the documentation for that string, it's name property will be formatted as:
projects/_/instances/<databaseInstance>/refs/<databasePath>
The databaseInstance string is what you're looking for. So, you can just split the string on "/" and take the 4th element of that array:
export const yourFunction = functions.database
.instance('yourShard')
.ref('yourNode')
.onCreate((snap, context) => {
const parts = context.resource.name.split('/')
const shard = parts[3]
console.log(shard)
})
If all you need is a reference to the location of the change, in order to perform some changes there, you can just use the ref property on the DataSnapshot that was delivered in the first argument, and build a path relative to there.

Firestore/Cloud functions: Finding a document in array of document references that match criteria

Using Firebase Cloud Functions I'd like to search for documents that contain a certain other document in an array of document references. My structure looks as follows;
Users
name
email
cars
ref to cars/car1 for example
ref to cars/car2 for example
Cars
registration
make
model
There are multiple users and multiple cars. I need to search for users that have a certain 'car' in their car array.
I'm trying to write this in a Cloud Function and have the following;
admin.firestore()
.collection('users')
.where('cars', 'array-contains', registration)
.get().then(doc => {
console.log("TESTING: found the user " + doc.data().email)
return
}).catch(error => {
console.error(error);
});
I know this is currently just searching for the registration string in the array. Is there anyway to search for a specific document reference. I'm using Node.js.
Working code to get all the documents that have a document reference in an array;
// Notify the owner of the car
admin.firestore()
.collection('users')
.where('cars', 'array-contains', carRef)
.get().then(snapshot => {
snapshot.forEach(doc => {
console.log("TESTING found the user " + doc.data().email);
const message = {
notification: {
body: 'Your vehicle (' + carReg + ') recieved a report. Tap here to see!',
},
token: doc.data().cloudMessagingToken
};
sendMessage(message);
});
return
}).catch(error => {
console.error("Error finding a user that has the car in their garage");
console.error(error);
});
If you want to query using reference type fields, you will need to provide a DocumentReference type object to the query. If you pass a DocumentReference to a car, the query should work. For example:
const ref = admin.firestore().collection('Cars').doc(id)
where id is the id of the document.
However, you can't search using values of fields inside the referenced document. Firestore queries only work against data in a single collection at a time. With the way you have your data organized right now, it's not possible to make a single query for all users who have references to cars with a specific registration string field. For that query, you would need to also store an array of registration strings for each user that you could query with array-contains.
Yes, this involves duplication of data, and it's called "denormalization". This is very common in nosql type databases to enable the queries you need.

How to add a document to a collection in cloud firestore

I have a collection called 'users'. I'm trying to add a user to the collection after Google authentication but I keep getting the following error:
FirebaseError: [code=invalid-argument]: Invalid document reference. Document references must have an even number of segments, but users has 1.
Here is the code
this.googlePlus.login({
'scopes': '',
'webClientId': environment.googleWebClientId,
'offline': true,
})
.then(user => {
// save user data on the native storage
const userRef: AngularFirestoreCollection<User> = this.afs.collection<User>(`users/`);
const data: User = {
email: user.email,
displayName: user.displayName,
uid: user.uid
};
userRef.set(data)
.then(() => {
this.router.navigate(['/home']);
Google+ is being discontinued so you should look at Firebase Authentication, or GCP's new Cloud Identity Platform.
In the case of Firebase Authentication, you must listen to the .onAuthStateChanged observer. Once it fires off your user object, you then take that and write a new user document to a users collection in Firestore. Best practise is to use the uid of the firebase.auth().currentUser.uid as the user document ID in your users collection.
Your userRef refers to a collection, and the type of object is called a CollectionReference. You're attempting to call set() on it with some object that should become a new document in that collection. But that's not the way it works. Instead, it looks like you want to call add() to add a new document with a new random ID.
If you somehow already know the ID of the new user document, you should build a DocumentReference with that id, then use set() on that DocumentReference to create the document.

how do I get the dataID when using cloud firestore triggers function?

I have an Event Collection in the Firestore database like this:
I want to use cloud firestore triggers. when a user attends an event, the capacity of the event will be -1, and when this field is updated I want to automatically update another field ("rankPoint") +3
to implement this, I need to Trigger a function when a document is updated
from firestore documentation, it will be like this
exports.updateUser = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = change.after.data();
// ...or the previous value before this update
const previousValue = change.before.data();
// access a particular field as you would any JS property
const name = newValue.name;
// perform desired operations ...
});
for my case, it should be 'events/{eventId}' right? but how do I get that eventID in the wildcard? does it come from client side? I mean in iOS/Android I will write the code to update like
db.collection("eventss").document("someEventIDHere").setData(data)
is it from the client?
Your function will only be delivered the document that matched your function's pattern (users/{userId}) that was changed. Other documents are not available until you query for them. So, if you want a document from you events collection, you will have to write some code to access it, then decide what to do from there.
It sounds like you're expecting there to be an eventId wildcard, but there is not. There is just the userId wildcard that you defined in your function. Other values will need to be derived from the data you have available in your user document.

Resources