7

I'm trying to model "memberships" with Firestore. The idea is that there are companies, users and then memberships.

The memberships collection stores a reference to a company and to a user, as well as a role as a string, e..g admin or editor.

How would I query to get the users with a certain role for a company?

This is what I currently have with some basic logging.

const currentCompanyID = 'someid';

return database
.collection('memberships')
.where('company', '==', database.doc(`companies/${currentCompanyID}`))
.where('role', '==', 'admin')
.get()
.then(snap => {
  snap.forEach(function(doc) {
    console.log(doc.id, ' => ', doc.data());
    const data = doc.data();
    console.log(data.user.get());
  });
})
.catch(error => {
  console.error('Error fetching documents: ', error);
});

data.user.get() returns a promise to the user, but I'd have to do that for every user which seems inefficient?

What would be the best way to approach this?

Steve Sharpe
  • 81
  • 1
  • 5
  • Possible duplicate of [Cloud Firestore deep get with subcollection](https://stackoverflow.com/questions/46611279/cloud-firestore-deep-get-with-subcollection) – solocommand Nov 21 '17 at 02:11

1 Answers1

0

Your code is close to what you want, but there are two issues:

  • Your where() clause can't compare a field with a document reference, because Firestore is a classic denormalized datastore. There aren't ways to strongly guarantee that one document refers to another. You'll need to store document IDs and maintain consistency yourself. (Example below).
  • Queries actually return a QuerySnapshot, which includes all the docs that result from a query. So you're not getting one document at a time — you'll get all the ones that match. (See code below)

So a corrected version that fits the spirit of what you want:

const currentCompanyID = '8675309';
const querySnapshot = await database
  .collection('memberships')
  .where('companyId', '==', currentCompanyID)
  .where('role', '==', 'admin')
  .get();  // <-- this promise, when awaited, pulls all matching docs

await Promise.all(querySnapshot.map(async snap => {
  const data = doc.data();
  const user = await database
    .collection('users')
    .doc(data.userId)
    .get();

  console.log(doc.id, ' => ', data);
  console.log(user);
});

There isn't a faster way on the client side to fetch all the users that your query refers to at once -- it's part of the trouble of trying to use a denormalized store for queries that feel much more like classic relational database queries.

If this ends up being a query you run often (i.e. get users with a certain role within a specific company), you could consider storing membership information as part of the user doc instead. That way, you could query the users collection and get all the matching users in one shot.

Philip Su
  • 58
  • 5