4

I have a JSON structure like the following:

{
  "groups" : {
    "-KAv867tzVgIghmr15CM" : {
      "author" : "ruben",
      "name" : "Item A"
    },
    "-KAv87nqLEG1Jtc04Ebn" : {
      "author" : "ruben",
      "name" : "Item B"
    },
    "-KAv88yZe8KTfkjAE7In" : {
      "author" : "ruben",
      "name" : "Item C"
    }
  },
  "users" : {
    "rsenov : {
      "avatar" : "guest",
      "email" : "[email protected]",
      "groups" : {
        "-KAv867tzVgIghmr15CM" : "true",
        "-KAv87nqLEG1Jtc04Ebn" : "true",
        "-KAv88yZe8KTfkjAE7In" : "true"
      }
    }
  }
}

Every user has the element "groups" with a childByAutoId() key. Then I have the list of all the groups that exists in the app.

Every time that I run the app, I get the current user logged url reference, and I get the list of the groups of that user (in this case, the logged in user is "rsenov" that has 3 groups). For every group that this user belongs to, I iterate through the groups url reference, looking for getting the information of that 3 groups.

I do this like this:

func loadTable() {
    self.groups = []
    var counter = 0
    self.meses = []
    var tempItems = [String]()

    DataService.dataService.CURRENT_USER_GROUPS_REF.observeEventType(.Value, withBlock: { snapshot in

        if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
            tempItems = []
            for snap in snapshots {

                DataService.dataService.GROUPS_REF.childByAppendingPath(snap.key).queryOrderedByChild("name").observeEventType(.Value, withBlock: { snapshot in
                    if let postDictionary = snapshot.value as? Dictionary<String, AnyObject> {
                        tempItems.append(snapshot.value.objectForKey("name") as! String)
                        let key = snapshot.key
                        let group = Group(key: key, dictionary: postDictionary)
                        self.groups.insert(group, atIndex: 0)

                    }
                    counter++
                    if (counter == snapshots.count) {
                        self.meses = tempItems
                        self.miTabla.reloadData()

                    }
                })
            }
        }
    })
}

I think this is not a good idea of iterating in that way. For example, if there is a change of some child in the GROUPS_REF url, the code only runs in that nested code, and since it doesn't have the "snap.key" value got from the for loop, it doesn't work.

Which is the best way to do a good query in this case?

3
  • Sorry, I made it already. Didn't know this option was available Commented Feb 19, 2016 at 20:58
  • You are right, groups and users are two top-level nodes Commented Feb 19, 2016 at 21:15
  • To clarify; when the user logs in, you want to get the data from each group they belong to (group_id, author and name)? Commented Feb 19, 2016 at 22:11

2 Answers 2

3

Phew, that took some time to write. Mostly because I don't iOS/Swift a lot:

let ref = Firebase(url: "https://stackoverflow.firebaseio.com/35514497")
let CURRENT_USER_GROUPS_REF = ref.childByAppendingPath("users/rsenov/groups")
let GROUPS_REF = ref.childByAppendingPath("groups")

var counter: UInt = 0
var groupNames = [String]()

CURRENT_USER_GROUPS_REF.observeEventType(.Value, withBlock: { groupKeys in
    for groupKey in groupKeys.children {
        print("Loading group \(groupKey.key)")
        GROUPS_REF.childByAppendingPath(groupKey.key).observeSingleEventOfType(.Value, withBlock: { snapshot in
            print(snapshot.value)
            if (snapshot.childSnapshotForPath("name").exists()) {
                groupNames.append(snapshot.value.objectForKey("name") as! String)
            }
            counter++
            if (counter == groupKeys.childrenCount) {
                print(groupNames)
            }
        })
    }
})

By the way, this is how you create a minimal, complete verifiable example. The code has no external dependencies (such as Group and DataService in your code) and only contains what's relevant to the answer.

The important bits:

  • I used observeSingleEventOfType to get each group, since I don't want to get more callbacks if a group changes
  • I use snapshot.childSnapshotForPath("name").exists() to check if your group has a name. You probably want to either ensure they all have names or add them to the list with some other property in the real app.
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your answer Frank! However I am experiencing the same behavior as my code. If there's changes for example in the group name, the nested code won't be called, even if I replace it with observeEventType won't work because the groupKey.key value doesn't exists in that moment because the code doesn't run inside the for loop. Probably, the solution would be using the database structure proposed by @Jay
Yup, that looks good too. Whichever one you go with, click the checkbox next to it.
As you are an Engineer for Firebase at google, wouldn't be a good idea to make a suggestion about including a function that could help for this kind of query?
That's an interesting comment as I thought the same when first starting with NoSQL databases. Coming from a SQL background, SQL is essentially a 'table of rows and columns' so your effort is to structure a query to get the data from a row/column formatted table. NoSQL is a different beast where there are no tables and no pre-defined 'structure' (JSON), so the challenge is to structure the data to match the query.
And.... If you want to catch when a group name changes, per your comment, you could add an observer to the /groups node. If you are keeping the group names in code, you may want to consider creating a Group Class that has name and key as properties and keep those in an array (or dictionary) and when the name changes and you receive the event, use an NSPredicate on the array of objects for the key and update the name or NSDictionary for the key and update the name. If that doesn't make sense (hard to expound too much in a comment), ask another question and we will craft up a solution.
2

Frank's answer is on-point. I wanted to throw in an alternative that may or may not work for your situation as it requires a slight alteration to the database.

groups
  gid_0
    author: "ruben"
    name:   "Item A"
    users
      uid_0: true
 gid_1
    author: "ruben"
    name:   "Item B"
    users
      uid_1: true
 gid_2
    author: "ruben"
    name:   "Item C"
    users
      uid_0: true

And then some ObjC Code for a Deep Query

Firebase *ref = [self.myRootRef childByAppendingPath:@"groups"];

FQuery *query1 = [ref queryOrderedByChild:@"users/uid_0"];

FQuery *query2 = [query1 queryEqualToValue:@"true"];

[query2 observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
    NSLog(@"key: %@   value: %@", snapshot.key, snapshot.value);
}];

This code does a deep query on the /groups for all groups that have a /users/uid_0 = true. In this case it returns gid_0 and gid_2

It eliminates the need for iterations and multiple calls to the database.

Adding a /users/ node to each group with a list of the uid's may offer some additional flexibility.

Just a thought.

2 Comments

Thats a good Idea! I didn't think about using a structure like that. I'm not sure if it's the most optimal way to structure a database, but I think that in my case will work well
Disk space is cheap, and duplicated data or two-way references are common practice with NoSQL databases so don't be afraid to do that. There are times when you need the references to the groups in the /users node and at the same time you need a references in the /groups node to the users.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.