1

I have a pretty basic data structure

  • events
  • topics

I would like to be able to easily show (query)

  1. what topics are owned by an event

  2. what events cover a topics

  3. what are the most popular topics this month

I am pretty comfortable with my events structure like

/events/880088/topics.json *

["Firebase", "Cloud"]

but I struggle with how to structure the /topics nodes. I partially get the idea of going with something like

/topics/Firebase

{"12345":true,"88088":true}

and then if when I update an events's topic collection I would have to iterate over all the /topics/ nodes and update /topics/{{topic}}/{{eventid}} to {true | null}. Which seems rather ham fisted.

ALSO, then I am still at a loss of how to query say, what are the topics covered by events this month.

Example JSBin from comments below http://jsbin.com/dumumu/edit?js,output

* I know, I know, arrays are evil, https://www.firebase.com/blog/2014-04-28-best-practices-arrays-in-firebase.html, but I think they fit in this scenaris

Lloyd
  • 1,395
  • 6
  • 20
  • 37
  • I write a little snippet that build your structure: http://jsbin.com/hameja/edit?js,console. Can you edit your question with the code you use and what you feel is ham fisted about it? – Frank van Puffelen Sep 25 '15 at 16:16
  • A quick answer below. Well... not really an answer, but hopefully some helpful code snippets to compare to. – Frank van Puffelen Sep 25 '15 at 16:43

1 Answers1

2

Here's one way to add an event:

function addEvent(title, topics) {
  var event =ref.child('events').push({ title: title });
  topics.forEach(function(topic) {
    event.child('topics').child(topic).set(true);
    ref.child('topics').child(topic).child(event.key()).set(true);
  });
}

Seems pretty simple for me. For an interesting twist, you can use the new multi-location updates we launched yesterday (September 2015):

function addEvent(title, topics) {
  var updates = {};
  var eventId = ref.push().key();
  updates['events/'+eventId+'/title'] = title;
  topics.forEach(function(topic) {
    updates['events/'+eventId+'/topics/'+topic] = true;
    updates['topic/'+topic+'/'+eventId] = true;
  });
  ref.update(updates);
}

The latter is a bit more code. But it's a single write operation to Firebase, so there's no chance of the user closing the app between write operations.

You invoke both the same of course:

addEvent('Learn all about Firebase', ['Firebase']);
addEvent('Cloudspin', ['Firebase', 'Google', 'Cloud']);

And the data structure becomes:

{
  "events": {
    "-K-4HCzj_ziHkZq3Fpat": {
      "title": "Learn all about Firebase",
      "topics": {
        "Firebase": true
      }
    },
    "-K-4HCzlBFDIwaA8Ajb7": {
      "title": "Cloudspin",
      "topics": {
        "Cloud": true,
        "Firebase": true,
        "Google": true
      }
    }
  },
  "topic": {
    "Cloud": {
      "-K-4HCzlBFDIwaA8Ajb7": true
    },
    "Firebase": {
      "-K-4HCzj_ziHkZq3Fpat": true,
      "-K-4HCzlBFDIwaA8Ajb7": true
    },
    "Google": {
      "-K-4HCzlBFDIwaA8Ajb7": true
    }
  }
}

Querying/reporting

With Firebase (and most NoSQL databases), you typically have to adapt your data structure for the reporting you want to do on it.

Abe wrote a great answer on this recently, so go read that for sure: Firebase Data Structure Advice Required

Update: change the topics for an event

If you want to change the topics for an existing event, this function is once way to accomplish that:

function updateEventTopics(event, newTopics) {
  newTopics.sort();
  var eventId = event.key();
  var updates = {};
  event.once('value', function(snapshot) {
    var oldTopics = Object.keys(snapshot.val().topics).sort();
    var added = newTopics.filter(function(t) { return oldTopics.indexOf(t) < 0; }), 
        removed = oldTopics.filter(function(t) { return newTopics.indexOf(t) < 0; });
    added.forEach(function(topic) {
      updates['events/'+eventId+'/topics/'+topic] = true;
      updates['topic/'+topic+'/'+eventId] = true;
    });
    removed.forEach(function(topic) {
      updates['events/'+eventId+'/topics/'+topic] = null;
      updates['topic/'+topic+'/'+eventId] = null;
    });
    ref.update(updates);
  });
}

The code is indeed a bit long, but that's mostly to determine the delta between the current topics and the new topics.

In case you're curious, if we run these API calls now:

var event = addEvent('Cloudspin', Date.now() - month, ['Firebase', 'Google', 'Cloud']);
updateEventTopics(event, ['Firebase', 'Google', 'GCP']);

The changeEventTopics() call will result in this update():

{
  "events/-K-93CxuCrFDxM6k0B14/topics/Cloud": null,
  "events/-K-93CxuCrFDxM6k0B14/topics/GCP": true,
  "topic/Cloud/-K-93CxuCrFDxM6k0B14": null,
  "topic/GCP/-K-93CxuCrFDxM6k0B14": true
}
Community
  • 1
  • 1
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Ooooo. multi location updating. That is hawt. Thanks for that. :) Where I stumble a bit is the next step, say I change the topics later. How would I delete an event element from the topic collection? Without scanning all topic nodes? – Lloyd Sep 25 '15 at 20:50
  • Can you give an API call that you'd want for changing the topics? Code beats descriptions, every time. :-) (Agreed on the multi-location hotness btw) – Frank van Puffelen Sep 25 '15 at 21:29
  • OK, I took your jsbin and modified it to the point my wheels fall off my wagon. http://jsbin.com/dumumu/edit?js,output – Lloyd Sep 26 '15 at 03:11
  • OK, I added an example for that. Note that I reverted to an API-based approach, so that we can just focus on the back-end of things. Updated jsbin is here: http://jsbin.com/hameja/edit?js,console – Frank van Puffelen Sep 26 '15 at 14:39
  • perfect, it was finding that delta that was non obvious to me. – Lloyd Sep 27 '15 at 02:23