Adding a new field to nested subcollection in firebase [duplicate] - firebase

I have two collections. One collection "User", who contains the user info (username...) And one collection "Post" who contains all posts of my flutter application. A post document contains a "Text Post" and the "Username" of the writter. I add an option in my application to allow the user to change his nickname every 6 months. But I must change the username in the "User" collection and in all posts it creates in the "Post" collection. What is the best practice ?
The user make a query for update username in "User" collection, i intercept the "OnUpdate" in cloud function and i update all post in server side.
The user make a query for update username in "User" collection, and update all "Post" collection in client side.
I guess if i do a geDocuments () there is a limit, so I should do it in multiple times if I have too many "Post" Documents, am I correct?

There is no singular best practice here. Both approaches you describe are valid, and neither is pertinently better than the other.
A few things to keep in mind in either scenario:
You may not be able to handle all updates in a single batched write (since that can handle at most 500 documents at once), so I'd recommend not wasting energy on that.
In some scenario's it is also acceptable (and sometimes even required) to not update the existing documents, so I recommend always considering that too.

Related

Best Practice to keep user data in firebase firestore?

I am using firebase as a backend for my Android App. And this app is a social media app where users can post, comment & like. so I am storing user data in user_collection and this collection is secured by security rules where if uid == auth.uid (any user can only access data of himself).
Now in this app when a user post something every user can see this post. And in post_collection I am saving userId in post_doc.
So the problem is I need to show name of user to other users and I have only userId but the problem is a user can't get name of other user by uid beacuse of security rules. now I have to solutions for this please tell me which one is better or you can also suggest any other solutions also?
I can use cloud functions getUserNameById() (Problem : I need to call this function very frequently in feed when user scroll)
I can store name also in post_doc (problem : when user changes his name then It will show old name in old post)
Thanks for you kind help
In a scenario like the one you describe, I would typically store the user name in each post doc. I would also ignore updates to the name, as I think of the user name in the post doc as a historical value: this is the name the user had when they posted this. Now you may want different behavior of course, in which case I recommend reading: How to write denormalized data in Firebase
Your approach with Cloud Functions is fine too, and quite common in some situations. But I tend to only use Cloud Functions for reading data, it the read operation itself is particularly complex, which isn't the case here. In a case like this, I'd recommend coming up with a data model that allows the use-case and security you want.
For example: if you create a collection usernames where each document has the UID as its document ID, and then contains a single field with the username for that UID, you could implement the lookup of the user name through Firestore.
So you could have:
Store the full user profile in /users/$uid.
Store the user name in /usernames/$uid.
Have a Cloud Function that triggers when /users/$uid is written and that updates /usernames/$uid.
The client then has read access to each /usernames/$uid document, or even to the entire /usernames collection in one go if needed.
This way the names can be cached on the client, and continue to work when the app is offline, unlike in your approach with a Cloud Function that looks up the user name.
Consider the solution: whatever public data you need (author name, author userpic link etc) just save it with the post at the time it had created.
So your Message Pojo will looks like:
id
authorName
text
etc..
and just display this name (authorName).
It will be the bad way to go any time to User_collection folder to take the name even if there are would be not strict security (becouse it takes time and document reads)

Can I query for documents and filter some document fields out of the querySnapshot response using Firestore?

Imagine that I have a collection of documents:
collection:
'products'
And inside that collection I have documents, and each document has a really heavy field that I want filtered before it gets sent to my clients.
document product1:
{
id: 'someUniqueKey',
title: 'This is the title',
price: 35.00,
heavyField: [
'longStringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'longStringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'longStringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'longStringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'longStringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'longStringxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
Can I query for those documents inside the products collection and leave this specific heavyField out of my response?
I would like to be able to get the documents with 1 of their fields filtered out. Maybe select the fields I want to receive on my client.
Is this possible? Or in this case it's best to structure my data different and leave the heavyField in a different collection?
In SQL, this would be called a "projection" in a query. Cloud Firestore doesn't support projections from web and mobile client SDKs. You should instead put your "heavy" fields into documents in another collection, and query that only as needed.
If you're wondering why, it partly has to do with the way that the client local persistence layer works. It would make caching much more complicated if only certain fields existed locally for a given document. It's much more straightforward to simply fetch and store the entire document, so there is no question whether or not the document is locally available.
For the purpose of data modeling, it's best to think of documents as "atomic units", all or nothing, that can't be broken down.

Cloud Firestore and data modeling: From RDBMS to No-SQL

I am building an iOS app that is using Cloud Firestore (not Firebase realtime database) as a backend/database.
Google is trying to push new projects towards Cloud Firestore, and to be honest, developers with new projects should opt-in for Firestore (better querying, easier to scale, etc..).
My issue is the same that any relational database developer has when switching to a no-SQL database: data modeling
I have a very simple scenario, that I will first explain how I would configure it using MySQL:
I want to show a list of posts in a table view, and when the user clicks on one post to expand and show more details for that post (let say the user who wrote it). Sounds easy.
In a relational database world, I would create 2 tables: one named "posts" and one named "users". Inside the "posts" table I would have a foreign key indicating the user. Problem solved.
Poor Barry, never had the time to write a post :(
Using this approach, I can easily achieve what I described, and also, if a user updates his/her details, you will only have to change it in one place and you are done.
Lets now switch to Firestore. I like to think of RDBMS's table names as Firestore's collections and the content/structure of the table as the documents.
In my mind i have 2 possible solutions:
Solution 1:
Follow the same logic as the RDBMS: inside the posts collection, each document should have a key named "userId" and the value should be the documentId of that user. Then by fetching the posts you will know the user. Querying the database a second time will fetch all user related details.
Solution 2:
Data duplication: Each post should have a map (nested object) with a key named "user" and containing any user values you want. By doing this the user data will be attached to every post it writes.
Coming from the normalization realm of RDBMS this sounds scary, but a lot of no-SQL documents encourage duplication(?).
Is this a valid approach?
What happens when a user needs to update his/her email address? How easily you make sure that the email is updated in all places?
The only benefit I see in the second solution is that you can fetch both post and user data in one call.
Is there any other solution for this simple yet very common scenario?
ps: go easy on me, first time no-sql dev.
Thanks in advance.
Use solution 1. Guidance on nesting vs not nesting will depend on the N-to-M relationship of those entities (for example, is it 1 to many, many to many?).
If you believe you will never access an entity without accessing its 'parent', nesting may be appropriate. In firestore (or document-based noSQL databases), you should make the decision whether to nest that entity directly in the document vs in a subcollection based on the expect size of that nested entity. For example, messages in a chat should be a subcollection, as they may in total exceed the maximum document size.
Mongo, a leading noSQL db, provides some guides here
Firestore also provided docs
Hope this helps
#christostsang I would suggest a combination of option 1 and option 2. I like to duplicate data for the view layer and reference the user_id as you suggested.
For example, you will usually show a post and the created_by or author_name with the post. Rather than having to pay additional money and cycles for the user query, you could store both the user_id and the user_name in the document.
A model you could use would be an object/map in firestore here is an example model for you to consider
posts = {
id: xxx,
title: xxx,
body: xxx,
likes: 4,
user: {refId: xxx123, name: "John Doe"}
}
users = {
id: xxx,
name: xxx,
email: xxx,
}
Now when you retrieve the posts document(s) you also have the user/author name included. This would make it easy on a postList page where you might show posts from many different users/authors without needed to query each user to retrieve their name. Now when a user clicks on a post, and you want to show additional user/author information like their email you can perform the query for that one user on the postView page. FYI - you will need to consider changes that user(s) make to their name and if you will update all posts to reflect the name change.

Firebase query for bi-directional link

I'm designing a chat app much like Facebook Messenger. My two current root nodes are chats and users. A user has an associated list of chats users/user/chats, and the chats are added by autoID in the chats node chats/a151jl1j6. That node stores information such as a list of the messages, time of the last message, if someone is typing, etc.
What I'm struggling with is where to make the definition of which two users are in the chat. Originally, I put a reference to the other user as the value of the chatId key in the users/user/chats node, but I thought that was a bad idea incase I ever wanted group chats.
What seems more logical is to have a chats/chat/members node in which I define userId: true, user2id: true. My issue with this is how to efficiently query it. For example, if the user is going to create a new chat with a user, we want to check if a chat already exists between them. I'm not sure how to do the query of "Find chat where members contains currentUserId and friendUserId" or if this is an efficient denormalized way of doing things.
Any hints?
Although the idea of having ids in the format id1---||---id2 definitely gets the job done, it may not scale if you expect to have large groups and you have to account for id2---||---id1 comparisons which also gets more complicated when you have more people in a conversation. You should go with that if you don't need to worry about large groups.
I'd actually go with using the autoId chats/a151jl1j6 since you get it for free. The recommended way to structure the data is to make the autoId the key in the other nodes with related child objects. So chats/a151jl1j6 would contain the conversation metadata, members/a151jl1j6 would contain the members in that conversation, messages/a151jl1j6 would contain the messages and so on.
"chats":{
"a151jl1j6":{}}
"members":{
"a151jl1j6":{
"user1": true,
"user2": true
}
}
"messages":{
"a151jl1j6":{}}
The part where this gets is little "inefficient" is the querying for conversations that include both user1 and user2. The recommended way is to create an index of conversations for each user and then query the members data.
"user1":{
"chats":{
"a151jl1j6":true
}
}
This is a trade-off when it comes to querying relationships with a flattened data structure. The queries are fast since you are only dealing with a subset of the data, but you end up with a lot of duplicate data that need to be accounted for when you are modifying/deleting i.e. when the user leaves the chat conversation, you have to update multiple structures.
Reference: https://firebase.google.com/docs/database/ios/structure-data#flatten_data_structures
I remember I had similar issue some time ago. The way how I solved it:
user 1 has an unique ID id1
user 2 has an unique ID id2
Instead of adding a new chat by autoId chats/a151jl1j6 the ID of the chat was id1---||---id2 (superoriginal human-readable delimeter)
(which is exactly what you've originally suggested)
Originally, I put a reference to the other user as the value of the chatId key in the users/user/chats node, but I thought that was a bad idea in case I ever wanted group chats.
There is a saying: https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
There might a limitation of how many userIDs can live in the path - you can always hash the value...

How to entirely skip validation in simple schema and allow incomplete documents to be stored?

I'm creating an order form and a schema defined for an Order (certain required fields such as address, customer info, items selected and their quantities, etc).
a. User visits site.
b. A unique ID is generated for their session as well as a timestamp.
var userSession = {
_id: createId(),
timestamp: new Date(),
};
var sessionId = userSession._id;
c. The userSession is placed in local storage.
storeInLocalStorage('blahblah', sessionObject);
d. An Order object is created with the sessionId as the only field so far.
var newOrder = {
sessionId: sessionId;
};
e. Obviously at this point the Order object won't validate according to the schema so I can't store it in Mongo. BUT I still want to store it in Mongo so I can later retrieve incomplete orders, or orders in progress, using the sessionID generated on the user's initial visit.
This won't work because it fails validation:
Orders.insert(newOrder);
f. When a user revisits the site I want to be able to get the incomplete order from Mongo and resume:
var sessionId = getLocalStorage('blahblah')._id;
var incompleteOrder = Orders.findOne({'sessionId', sessionId});
So I'm not sure how to go about doing this while accomplishing these points.
I want full simpleschema validation on the Orders collection when the user is entering in items on the forms and when the user is intending to submit a full, complete order.
I want to disable simpleschema validation on the Orders collection and still allow storing into the DB so that partial orders can be stored for resumption at a later time.
I can make a field conditionally required using this here but that would mean 50+ fields would be conditionally required just for this scenario and that seems super cumbersome.
It sounds like you want to have your cake, and eat it too!
I think the best approach here would be keep your schema and validation on the Orders collection, but store incomplete orders elsewhere.
You could store them in another collection (with a more relaxed schema) if you want them on the server (possibly for enabling resume on another device for the logged in user) , or more simply in Local Storage, and still enable the resume previous order behaviour you are wanting.
Only write to the Orders collection when the order is complete (and passes validation).
Here's a variation on #JeremyK's answer: add an inProgress key to your order of type [Object]. This object would have no deeper validation. Keep your in progress order data in there until the order is final then copy/move all the relevant data into the permanent keys and remove the inProgress key. This would require that you make all the real keys optional of course. The advantage is that the object would maintain its primary key throughout the life cycle.
I think this particular case has been solved; but just in case, you can skip Simple Schemma validations by accessing MongoDB native API via Collection#rawCollection():
Orders.rawCollection().insert(newOrder);
While this question is very old in the meantime there is a better solution. You probably use simple schema together with collection2. Collection2 has the ability to set multiple schemas based on a selector and then validate against the correct schema based on it.
https://github.com/Meteor-Community-Packages/meteor-collection2#attaching-multiple-schemas-to-the-same-collection
e.g. you could have a selector {state: 'finished'} and only apply the full schema to these documents while having another selctor, e.g. {state: 'in-progress'} for unfinished orders with a schema with optional fields.

Resources