user9079075
user9079075

Reputation:

Cloud Functions for Firestore: accessing parent collection data

Many blogs suggest to switch to Cloud Firestore because it's easy and well secured. Coming from Realtime Database and back when using Functions + RD it was easy to navigate through document triggers, like ref.parent

My setup is like this:

  • Users
    • {userid}
      • last_seen: "data"
      • {forms}
        • {formid}

However, i have added a document trigger with onCreate, and i want to get the value of last_seen:

exports.updateUser = functions.firestore.document('users/{userId}/forms/{formid}').onCreate((snap, context) => {

    const newValue = snap.data();
    console.log("test value : " + newValue.test); // works
    console.log("form id: " + context.params.formid); // works
    console.log("user last seen : " + newValue.last_seen); // doesn't work, can't access the parent collection data


});

Upvotes: 6

Views: 7176

Answers (3)

Dennis Smolek
Dennis Smolek

Reputation: 8760

I totally get the confusion with the switch to Firestore but it's almost the exact same way in this case.

In realtime, you have the snapshot:

exports.doStuff = functions.database.ref('/users/{userId}/forms/{formId}')
    .onCreate((snapshot, context) => {
    const ref = snapshot.ref;
    const userRef = ref.parent.parent;
    userRef.once('value').then(parentSnap => {
        const user = parentSnap.val();
        const lastSeen = user.last_seen;
    });
});

In Firestore:

exports.doStuff = functions.firestore.document.onCreate('/users/{userId}/forms/{formId}')
    .onCreate((snapshot, context) => {
    const ref = snapshot.ref;
    const userRef = ref.parent.parent;
    userRef.get().then(parentSnap => {
        const user = parentSnap.data();
        const lastSeen = user.last_seen;
    });
});

Another thing to consider is you are passing the userId in your params so you could just build your own DocumentReference (assuming you're also using firebaseAdmin)

functions.firestore.document.onCreate('/users/{userId}/forms/{formId}')
    .onCreate((snapshot, context) => {
    const userId = context.params.userId;
    const userRef = firebaseAdmin.firestore().collection('users').doc(userId);
    userRef.get().then(parentSnap => {
        const user = parentSnap.data();
        const lastSeen = user.last_seen;
    });
});

It also allows you to decouple your logic for functions you may use often, consider it as a "helper" method: (NOTE, I switched to async/await on accident, it's a bit cleaner)

functions.firestore.document.onCreate('/users/{userId}/forms/{formId}')
    .onCreate(async (snapshot, context) => {
    const userId = context.params.userId;
    const lastSeen = await getLastSeen(userId);
});

// == Helper Functions ==-------------------
export async getLastSeen(userId) {
    if (!userId) return Promise.reject('no userId');
    // User Ref
    const userSnap = await firebaseAdmin.firestore().collection('users').doc(userId).get();
    return userSnap.data().last_seen;
}

Now you can use getLastSeen() whenever you need it, and if you make a change you only have to adjust that one function. If it's not something you call often then don't worry about it, but I would consider maybe a getUser() helper...

Upvotes: 27

Get the reference where the change took place, move 2 levels up and capture data using ref.once() function:

exports.updateUser = functions.firestore.document('users/{userId}/forms/{formid}').onCreate( async (snap, context) => {

    // Get the reference where the change took place
    const changeRef = snap.after.ref;

    // Move to grandad level (2 levels up)
    const userIdRef = changeRef.parent.parent;

    // Capture data
    const snapshot = await userIdRef.once('value');

    // Get variable
    const lastSeen = snapshot.val().last_seen;

    // Do your stuff...

    return null;

});

Upvotes: 0

Doug Stevenson
Doug Stevenson

Reputation: 317352

In your code, snap is a DocumentSnapshot type object. As you can see from the linked API documentation, there is a ref property on that object that gets you a DocumentReference object pointing to the document that was added. That object has parent property that gives you a CollectionReference that points to the collection where the document exists, which also has a parent property. So, use these properties to navigate around your database as needed.

Upvotes: 3

Related Questions