Search for particular value under all nodes in a directory - swift

I am struggling to figure out how to delete/look for a certain key/id under any given node in a particular directory.
Here is how my database looks:
The userID and postID is generated through the use of .childByAutoId(). So as you can infer, searching by a particular string .e.g. a username, is not possible. However, I do have the postID passed through the parameters, when calling the function. I have tried several methods, but no luck. In essence this is what I wish to do (pseudo code):
Go to the "feed" directory
The postID is passed already, so look through every node under feed
If the postID exists under a particular node, remove the node, if not, move onto the next node
Continue until the node does not exist in this particular directory
I do have a reference to my feed directory - API.Feed.REF_FEED. I have tried to use .parent , .queryOrdered, .queryEqualTo etc.

This will work:
let query = feedRef.queryOrdered(byChild: "-L....VJ").equalTo(true)
query.observe(.childAdded, with: { (snapshot) -> Void in
snapshot.ref.removeValue()
})
The problem with this approach though it that it requires that you have an index on -L....VJ, and thus on every post ID that you might want to delete. This rapidly becomes unmanageable.
For this reason consider adding an inverted lookup map to your database: one that maps each post ID to the users that have that post in their feed:
userposts
$postid
userid1: true
userid2: true
Now you can quickly find all the users that have a specific post, and then delete the post from those users' feed.

Related

Check for existing value inside of Firebase Realtime Database

Hello, I have a problem I created a Registration form and im trying to check if there is any user which have a certain username inside the Firebase Db. I tried to get the reference of all the users.
var users = Database.database().reference("users")
But I don't know how I could check if there is any user with a specified username.
You'll want to use a query for that. Something like:
let query = users.queryOrdered(byChild: "username").equalTo("two")
Then execute the query and check whether the result snapshot exists.
Note though that you won't be able to guarantee uniqueness in this way. If multiple users perform the check at the same time, they may both end up claiming the same user name.
To guarantee a unique user name, you will need to store the user names as the key - as keys are by definition unique within their parent node. For more on this, see some of these top search results and possibly also from here.

Deleting parent node with child value

I am struggling to overcome to issue I have. I am trying to delete a parent node/key given I find the correct child value.
My database is structure liked this
I am querying my database by a certain value, objectID, as it will match the postID which is passed through the parameters. The objectID value is removed. However, I am struggling to remove the key in which it falls under.
I have had mixed results so far:
I can either remove the objectID value using this code:
refSnap?.ref.child("objectID").child(postID).removeValue()
Of I can remove the whole notifications node/directory, using this:
refSnap?.ref.child("objectID").queryEqual(toValue: postID).ref.removeValue()
refSnap?.key gives me all the keys/nodes under the notifications node.
I cannot access the key which the objectID and all other information is stored under as it is .childByAutoId. Can anyone possibly help me as to how I can sort this issue?
While the other answer provides some insight with an alternate and workable structure, the ability to delete a node based on a child is fairly straightforward and directly addresses the question without changing the structure.
(note that the structure may need to be changed anyway but for this exercise, we'll use it as is.)
Given a structure suggested in the question:
notification
"rtupy..." //childByAutoId
"-LFEMjAcny..." // childByAutoId
"-LFEzrrq..." // childByAutoId
from: "aw,sdasdad"
objectID: "-LFEMjAcn...."
timestamp: 15292
type: "comment"
Suppose you want to delete the node "-LFEzrrq..." as shown in your screenshot. That node contains the child objectID: "-LFEMjAcn...."
To delete the node you need query for the node that contains the objectID you want which, according to the question, is working and returning the correct child.
Use the returned snapshot to obtain it's parent key, and get the path to that node and delete it. Note that we don't know what process or code the OP used to obtain the node they want to delete - perhaps it was from another query and the node reference was passed in or some other means.
let queryRef = //unknown how, but build the query for objectID = "-LFEMjAcny...."
queryRef.observeSingleEvent(of: .value, with: { snapshot in
let key = snapshot.key //this is the parent key of the objectID node i.e. -LFEMzrrq..."
let parentRef = snapshot.ref.parent! //this is the path to that parent
let refToDelete = parentRef.child(key) //add the parent key to the path: -LFEzrrq
refToDelete.removeValue() //delete it
})
As you can see, regardless of the parent nodes' key, or how deep it is, this code will delete the node found in the query.
The key names do not matter so using .childByAutoId as references to tie your nodes together is safe and generally best practice as disassociating node keys from the data they contain makes your structure highly expandable.
Your issue is that you are using childByAutoId and you can't "tie" these random alphanumerics to something(in this case the post).
The structure that i would set is this:
-notifications
--uid //notifications for user
--- userAid+userBid //follow notification, if they unfollow you already now which one it is and you can go and delete it
---commentNotificationID // you give this notification the same Id that the comment has, so if the user deletes the comment you use that id to delete the notification as well.

Firebase second node deep query without knowing the value of the higher node key (swift)

I have the following Firebase structure
There is a node that keeps a list of albums by user. In certain circumstances I do not have the user id (E7Bv..), I only have the album id (-L0uG...). If I had both then I could easily access the specific album node.
Since I do not have the userid, i need a way to query the node where the albumin is equal to the value I have in hand. I do not see how to structure such a query.
As one approach I also tried to add the albumid (-L0uG...) to the sub node as the value of _key. I'd prefer not to have to duplicate that value and just query for where the sub-node for albumid equals the value I have in hand for albumid.
Or, if that can not be done then can anyone tell me how to query where the sub-node has a value for _key that matches the value i have in hand - Without knowing the userid node value?
I would like to do something like this ... (where albumRef is the top node for byUser_albums)
albumRef.queryOrdered(byChild: "_key").queryEqual(toValue: albumID).observeSingleEvent(of: .value, with: { snapshot in
of course this fails because _key is not on the userid node, it is on the albumid node. I need to either query by the subnode id, or go deeper to query by _key
UPDATE
From Frank Below. I tried this ...
albumRef.queryOrdered(byChild: albumID).queryStarting(atValue: nil).observeSingleEvent(of: .value, with: { snapshot in
This works and finds the right data. It returns the key of the scoping autoid. The only downside is that Firebase says (in debug log) ....
"**Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding ".indexOn": "-L0vTQtLwBe_hilTOGid" at /byUser_albums to your security rules for better performance**"
If I were doing a typical read then I could just add the rule to the database, but I can't add this rule because the value that it wants the .indexOn is not a static value - it's an auto. I don't see a way to index on the autoid. I think I will have to restructure the albums node to have user as an attribute instead of defining a scoping node by user. I do not see a way to read without downloading all data and filter on the client.

Ordering Firebase posts Chronologically Swift

I have added posts to firebase and I am wondering how I can pull the posts chronologically based on when the user has posted them.
My Database is set up like below
The first node after comments is the User ID and then the posts are underneath that. Obviously, these posts are in order, however if a new user posts something in between "posting" and "another 1" ,for example, how would I pull that so it shows up in between.
Is there a way to remove the autoID and just use the userID as a key? The problem I am running into is the previous post is overwritten then.
I am accepting the answer as it is the most thorough. What I did to solve my problem was just create the unique key as the first node and then use the UID as a child and the comment as a child. Then I pull the unique key's as they are in order and find the comment associated with the uid.
The other answers all have merit but a more complete solution includes timestamping the post and denormalizing your data so it can be queried (assuming it would be queried at some point). In Firebase, flatter is better.
posts
post_0
title: "Posts And Posting"
msg: "I think there should be more posts about posting"
by_uid: "uid_0"
timestamp: "20171030105500"
inv_timestamp: "-20171030105500"
uid_time: "uid_0_ 20171030105500"
uid_inv_time: "uid_0_-20171030105500"
comments:
comment_0
for_post: "post_0"
text: "Yeah, posts about posting are informative"
by_uid: "uid_1"
timestamp: "20171030105700"
inv_timestamp: "-20171030105700"
uid_time: "uid_1_20171030105700"
uid_inv_time: "uid_1_-20171030105700"
comment_1
for_post: "post_0"
text: "Noooo mooooore posts, please"
by_uid: "uid_2"
timestamp: "20171030110300"
inv_timestamp: "-20171030110300"
uid_time: "uid_2_20171030110300"
uid_inv_time: "uid_2_-20171030110300"
With this structure we can
get posts and their comments and order them ascending or descending
query for all posts within the last week
all comments or posts made by a user
all comments or posts made by a user within a date range (tricky, huh)
I threw a couple of other key: value pairs in there to round it out a bit: compound values, query-ing ascending and descending, timestamp.
You can not use the userID as key value instead of the autoID, because the key must be unique, thats why Firebase just updates the value and does not add another one with the same key. Normally Firebase nodes are ordered chronologically by default, so if you pull the values, those should be in the right order. However if you wanna make sure about that, you can add a timestamp value and set a server timestamp. After pulling the data you can order it by that timestamp (I think there is actually a timestamp saved automatically by firebase that you can access somehow, but you need to look that up in the documentation). If I got it right, in order to accomplish what you want, you need to change the structure of your database. For example you could maybe use the autoID but save the userID you wanted to use as key as a value if you need that. Hope I got your idea right, if not just be more precise and I will try to help.
Firebase keys are chronological by default - it's built into their key generation algorithm. I think you need to restructure/rethink your data.
Your POSTS database should (possibly) have the comments listed with each post, and then you can duplicate on the user record if needed for faster retrieval if they need to be accessed by user. So something like:
POSTS
- post (unique key)
- title (text)
- date (timestamp)
- comments
- comment (unique key)
- text (text)
- user_id (user key)
- date (timestamp)
When you pull the comments, you shouldn't be pulling them from a bunch of different users. That could result it a lot of queries and a ton of load time. Instead, the comments could be added (chronologically of course) to the post object itself, and also to the user if you want to keep a reference there. Unlike in MySQL, NoSQL databases can have quite a bit of this data duplication.

What is a CloudKit RecordID

While starting to work more with CloudKit I just realized that I don't actually know what a RecordID is...
I'm looking at the CloudKit dashboard right now, I see RecordTypes, RecordName etc, but I don't see RecordID. The iOS Dev library mentions RecordID a lot, but never actually tells what it is or where to find it.
I guess I'm just dumb, but I can't figure it out.
Every record has a record Id, which is a CKRecordID instance, and the class has a name property. If you don't specify a name, new records will have a record Id with a name that is a GUID.
You can only fetch with record Id if you know it, and in most instances you will let Cloud Kit create on for you, and you won't store it locally, so you won't know it.
Every CKRecord has a bunch of metadata, which includes the record id. See the list here.
Here a snapshot of the dashboard, your recognised it. You can either accept the recordIDs that CloudKit gives you or generate your own [although they must be unique within your database instance]. Here the screenshot
And here a small code snippet to show you how to create your own ID, using the same method I suspect CloudKit uses.
let uniqueReference = NSUUID().UUIDString
let uniqueRecordID = CKRecordID(recordName: uniqReference)
let newRecord = CKRecord(recordType: "Collection", recordID:uniqueRecordID)