Nested Key Query in Firebase? - ios

Firebase Data Structure
{
"books": {
"-KaKjMMw-WQltqxrGEmj": {
"categories": {
"cat1": true,
"cat2": true
},
"author": "user1",
"title": "event1"
},
"-KaKjMMw-WQltqxrGEmk": {
"categories": {
"cat1": true,
"cat2": false
},
"author": "user1",
"title": "event2"
}
}
}
Query To find all books of a particular author
FNode.testNode.child("books")
.queryOrderedByChild("author")
.queryEqualToValue("user1")
.observeEventType(.Value) { (snapshot) in
print(snapshot)
}
Question:
I want to find all the books belonging to cat1. Couldn't figure out the query to do that.

After a lot of hit and trial, finally got my answer.
For the above structure. If you want to find all the books belonging to cat1 Here is the query for that:
FNode.testNode.child("books")
.queryOrderedByChild("categories/cat1").queryEqualToValue(true)
.observeEventType(.Value) { (snapshot) in
print(snapshot)
}
Note: FNode.testNode could be any node of type FIRDatabaseReference
To Firebase Team: Can you please include a sample of all possible firebase queries in data structures and put it alongside firebase docs. It's kind of hit-and-trial for us now.

Related

How can I limit Firebase Realtime db items to "users" who exist in an object

I have a realtime db all setup and working. The data structure is very simple:
Item
some: info
some: other info
Item 2
some: info
some: other info
My rules are also super simple:
{
"rules": {
".read":"auth.uid != null",
".write":"auth.uid != null"
}
}
The issue (obviously) is that while I am forcing a user to be authenticated, that's all I am requiring and any user can access all the items in the db.
What I want is a way to limit a user to an item.
something like:
Item1
some: info
some: other info
user_1: auth.uid
user_2: auth.uid2
Item2
some: info
some: other info
user_1: auth.uid3
user_2: auth.uid4
I can store that data but I am not sure how to structure my rules to limit that.
My actual json looks like:
{
"annotations": {
"8df0309f-dc62-821e-dd65-f0ad46396937": {
"author": "1OXVKN3Y5Z-11",
"xfdf": "LONG STRING"
}
},
"complete": false,
"created_at": "2020-09-01T17:52:25.653Z",
"field_values": {
"field_name": {
"name": "copy",
"value": "TEsting",
"widgetID": "e61e3abf-7cdd-7d07-daec-6c3d3a55d667"
}
},
"stamp_count": 0
}
What I plan to implement is:
{
"annotations": {
"8df0309f-dc62-821e-dd65-f0ad46396937": {
"author": "1OXVKN3Y5Z-11",
"xfdf": "LONG STRING"
}
},
"complete": false,
"created_at": "2020-09-01T17:52:25.653Z",
"field_values": {
"field_name": {
"name": "copy",
"value": "TEsting",
"widgetID": "e61e3abf-7cdd-7d07-daec-6c3d3a55d667"
}
},
"stamp_count": 0,
"users": [ "CFX4I0PTM9-11", "CFX4I0PTM9-7"]
}
One I implement that json structure, how can I setup rules to support?
From reading your question and the comment thread I think your requirement is:
Allow a user to access an item if their UID is associated with that item.
In that case, you'll first need to ensure that the UIDs are in keys, as you can't search across multiple values, as your proposed users array would require. So you'd end up with:
"items": {
"item1": {
...
"users": {
"CFX4I0PTM9-11": true,
"CFX4I0PTM9-7": true
}
}
}
Now with this structure, you can ensure a user can only update items where their UID is in the users map with rules like this:
{
"rules": {
"items": {
"$itemid": {
".write": "data.child('users').child(auth.uid).exists()"
}
}
}
}
For reading the specific item you could use a similar rule. That will allow the user to read an item once they know its complete path, and when their UID is in the users map.
But you won't be able to query this structure, as you can only index on named properties. For more on this, and the alternative data structure to still implement you use-case, see Firebase query if child of child contains a value

Hey #AskFirebase, how should I design this schema?

I'm building an app in Firebase and could use some advice. Here are the key points:
The app is for tracking time worked by employees.
Users are divided into teams, with each team having a manager.
As users clock in and out, the app tracks how long they work.
I'm stuck on a design problem. Here are the requirements:
Managers can read & write the time cards for all users on their team, while users can only read & write their own.
When users are viewing their own time cards, the UI needs to show most recent time cards sorted by timestamp.
When managers are viewing the time cards of everyone on their team, the UI needs to show most recent time cards sorted by timestamp.
Solving for all 3 requirements simultaneously is proving to be a challenge. My first hunch was to structure the time cards like this:
{
"timeCards": {
"-teamId_1": {
"-userId_1": {
"-timeCard_1": {
"startsAt: 1234567890123,
"endsAt": 1234567899999,
"duration": 2234567
},
"-timeCard_2": "..."
}
}
}
}
This solves the first two requirements easily:
Easy to write the permission rules such that managers have read/write for the entire team, but users can only read/write their own.
Easy to query a specific user's hours sorted by timestamp w/ db.ref('timeCards/${tid}/${uid}').orderByChild('startsAt')
Unfortunately, to solve the 3rd requirement with this schema requires some heavy lifting on the client. There is no way (that I'm aware of) to query Firebase for all of a team's time cards sorted by timestamp. So, I'd have to pull down all time cards for the team, then merge & sort them on the client.
I'm thinking this calls for denormalization, maybe a structure like this:
{
"timeCards": {
"byTeam": {
"-teamId_1": {
"-timeCard_1": {
"userId": "-userId_1",
"startsAt: 1234567890123,
"endsAt": 1234567899999,
"duration": 2234567
},
"-timeCard_2": "..."
}
},
"byUser": {
"-userId_1": {
"-timeCard_1": {
"teamId": "-teamId_1",
"startsAt: 1234567890123,
"endsAt": 1234567899999,
"duration": 2234567
},
"-timeCard_2": "..."
}
}
}
}
But this gets complicated on the server, requiring two Firebase functions, one watching timeCards/byTeam and one watching timeCards/byUser, that each mirror the records into the other collection.
I'm not sure how to avoid an infinite update loop in that situation, though. (Imagine updating timeCards/byTeam/-teamId_1/-timeCard_1, which fires the Firebase function and updates timeCards/byUser/..., which fires the Firebase function and updates timeCards/byTeam/..., etc.)
Am I missing an easy solution?
According to the Firebase docs good practice is to keep data as flat as possible. So it could look like this:
{
"users": {
"-userId_1": {
"name": "John"
},
"-userId_2": {
"name": "Ann"
}
},
"teams": {
"-teamId_1": {
"name": "Gladiators"
}
},
"timeCards": {
"-timeCard_1": {
"startsAt": 1234567890123,
"endsAt": 1234567899999,
"duration": 2234567,
"userId": "-userId_1",
"teamId": "-teamId_1"
},
"-timeCard_2": {
"startsAt": 1234567890123,
"endsAt": 1234567899999,
"duration": 2234567,
"userId": "-userId_2",
"teamId": "-teamId_1"
}
},
"usersInTeams": {
"-teamId_1": {
"-userId_1": true,
"-userId_2": true
}
},
"teamsInUsers": {
"-userId_1": {
"-teamId_1": true
},
"-userId_2": {
"-teamId_1": true
}
},
"timeCardsInUsers": {
"-userId_1": {
"-timeCard_1": true
},
"-userId_2": {
"-timeCard_2": true
}
},
"timeCardsInTeams": {
"-teamId_1": {
"-timeCard_1": true,
"-timeCard_2": true
}
}
}
Then you could write a Cloud Function that triggers when you write to "timeCards" (node: /timeCards/{pushId}) and adds timeCard ID to "timeCardsInUsers" (node: /timeCardsInUsers/{userId}) and "timeCardsInTeams" (node: /timeCardsInTeams/{teamId}).
It should resolve the looping problem because you write to different nodes.

Firebase iOS - Query with one part of the compound key

In my Firebase database I use a compound key as the ID for one-to-one conversations in the following format 'UID1_UID2' sorted lexicographically.
When I want to load all conversations via the iOS client for a particular user (UID1) how can I query Firebase when the compound key consists of two combined user IDs.
queryStartingAt("UID1_").queryEndingAt("UID1_")
will return all conversations UID1_ started.
You should be structuring your data as
conversation: UID1_UID2
Compound keys are not that great of an idea. Please consult this part of the documentation that explains how to best model a chat / conversation structure.
Relevant excerpt demonstrating how to structure data for chats:
{
// Chats contains only meta info about each conversation
// stored under the chats's unique ID
"chats": {
"one": {
"title": "Historical Tech Pioneers",
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
"timestamp": 1459361875666
},
"two": { ... },
"three": { ... }
},
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
// Messages are separate from data we may want to iterate quickly
// but still easily paginated and queried, and organized by chat
// conversation ID
"messages": {
"one": {
"m1": {
"name": "eclarke",
"message": "The relay seems to be malfunctioning.",
"timestamp": 1459361875337
},
"m2": { ... },
"m3": { ... }
},
"two": { ... },
"three": { ... }
}
}

How to retrieve related Objects in Firebase Database in Swift (or Web)?

I have data saved to a Firebase Database with complex relationships indexed as outlined here similar to the example below.
{
"groups": {
"alpha": {
"name": "Alpha Group",
"members": {
"mchen": true,
"hmadi": true
}
},
...
},
"users": {
"mchen": {
"name": "Mary Chen",
// index Mary's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"alpha": true
},
},
...
},
"animals": {
"dog": {
"users": {
// the value here doesn't matter, just that the key exists
"mchen": true
},
},
...
},
}
For my project, what I need to do is I need to get a list of group members for all users who like dogs. This means means getting one reference, then another, then another. If I want to get the group members of people who like dogs, I'd Fetch
animals>dog>mchen
user>mchen>groups>alpha
groups>alpha ... members(final result)
Given that this is all done asynchronously, how would I conduct these data retrievals with conditions of completion, chaining one to the next? I've already tried nesting completion handlers, and this doesn't seem to work.

Firebase queryOrderedByChild not ordering by name in Swift

I have in the security & rules option this:
{
"rules": {
".read": true,
".write": true,
"groups": {
".indexOn": "name"
}
}
}
And my JSON structure is like this:
{
"groups": {
"-KAti17inT7GbHEgbzzS": {
"author": "ruben",
"name": "C"
},
"-KAti36BQGO8sRmfufkE": {
"author": "ruben",
"name": "D"
},
"-KAti2CVAHtJllQm_m-W": {
"author": "ruben",
"name": "A"
}
}
As you can see, it is not ordered by "name", as is it supposed to be.
What I'm doing wrong?
That .indexOn instruction in the security rules only tells the database to create an index on the name property. It doesn't automatically re-order the data.
In addition: the order of keys in a JSON object is undefined.
To get a list of your groups by name, you have to execute a Firebase query. From the Firebase documentation on queries:
let ref = Firebase(url:"https://dinosaur-facts.firebaseio.com/dinosaurs")
ref.queryOrderedByChild("height").observeEventType(.ChildAdded, withBlock: { snapshot in
if let height = snapshot.value["height"] as? Double {
println("\(snapshot.key) was \(height) meters tall")
}
})
If you adapt this snippet to your data structure, it will print the groups in the correct order.

Resources