Trigger function when data is updated?

Hi there,

I’ve recently transitioned from using Firebase to Parse for macOSicons.com, and it has been great, but something I can’t seem to find is a way to run cloud function that updates my Algolia search index whenever an item on the Parse database is edited. Is this something currently possible?

Thanks again!!

You could use the afterSave or beforeSave Hook in Cloud code.
If you need to check if a specific column is edited, you can check with request.object.dirty(key) if a value has changed.

1 Like

I am playing with that right now and it seems that dirtyKeys() is available only in beforeSave. In the afterSave trigger it returns an empty array within the same save request. Am I missing something, or is that correct? Thank you!

Hey!
I think the dirtyKeys are missing in afterSave because they are already saved.
It makes no sense there.

Greetings,

You can also do something like:

Parse.Cloud.afterSave('className', ({object, original = new Parse.Object()}) => {
  if (original.get('key') !== object.get('key')) {
    console.log('key has been updated');
  }
});

Or, you could pass dirtyKeys through using request.context.

1 Like

Hey Daniel,

Why there need to be new new Parse.Object() ?

As I browse through documentation triggerRequest has the object there already. I am very unexperienced with javaScript, so I apologise for eventually silly question

Forgive me, it should be object, original = new Parse.Object().

The reason I do this is because original is undefined if it’s a new Parse.Object, and an error of “cannot get key of undefined” will throw. With JS, this notation is object destructuring + default parameters.

It’s pretty much the same as:

Parse.Cloud.afterSave('className', request => {
  const object = request.object;
  const original = request.original;
  if (!object.existed()) {
    // object is new
    return object;
  }
  if (original.get('key') !== object.get('key')) {
    console.log('key has been updated');
  }
});
1 Like

Oh, thanks for very clear explanation, it makes sense indeed! This is all new for me in javaScript.

Hey Daniel, I tried your trick, but I still struggle get it right with the following code:

Parse.Cloud.afterSave("PrsProfile", async ({object, original = new Parse.Object(), log}) => {
    const newPh = object.get('ph');
    const origo = original.get('ph');
    log.info(`photo urls has changed: ${(origo !== newPh)}, new: ${newPh}, old: ${origo}`);
    if (origo !== newPh) {
        try {
            await checkPhotosForDelete(object.id, newPh, log);
        } catch (e) {
            log.error(`error checkPhotosForDelete: ${e}`);
        }
    }
});

As you can see here, it see it that they are not the same, even they are. It has no effect if I use !== or != for comparison:

photo urls has changed: true,
new: ,file:///https:/parsefiles.back4app.com/coYfuTYV3z35Z8iwv8q3NOTdGQs2ywJtt7tUUg4p/7e04ef50c3364e2acaa7b7559ae85681_v00_1.jpg
old: ,file:///https:/parsefiles.back4app.com/coYfuTYV3z35Z8iwv8q3NOTdGQs2ywJtt7tUUg4p/7e04ef50c3364e2acaa7b7559ae85681_v00_1.jpg

I think there might be something wrong in how it takes the array? Because here I have an Array of optional Strings [String?] and later in the code I am trying to check if any of the saved urls are missing in the newPh array:

checkPhotosForDelete = async function(profileId, newPhotoUrlsArray, log) {
    if (Array.isArray(newPhotoUrlsArray) == false) {
        throw `newPhotoUrlsArray is not an array ${newPhotoUrlsArray}`;
    }
    const filesQuery = new Parse.Query("FileObj");
    filesQuery.equalTo('ow', profileId);
    filesQuery.find({useMasterKey: true}).then((fileObjects) => {
        log.info(`found fileObjects: ${fileObjects.length}`);

        for (let i = 0; i < fileObjects.length; ++i) {
            const profilePhoto = fileObjects[i].get("file");
            const url = profilePhoto.url();
            log.info(`includes: ${newPhotoUrlsArray.includes(url)}, url ${url}`);
            if (newPhotoUrlsArray.includes(url) == false) {                
                return profilePhoto.destroy({ useMasterKey: true });
            }
        }
    }).catch((error) => {
        log.info(`Error checking photos to delete ${error.code}: ${error.message}`);
    });
}

and there is the line newPhotoUrlsArray.includes(url) returning false even when the url is in the array. I believe I got lost again in the javaScript weak typing or how it is called.

As usual, my bad. The first comparison of two array I solved with stackOverflow inspiration:

function arrayIsSame(array1, array2) {
    if (array1.length != array2.length) { 
        return false
    }
    for (var i = 0, l=array2.length; i < l; i++) {
        if (array1[i] != array2[i]) { 
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;   
        }
    }
    return true
};

and in the second point I was saving wrong string in arrays:

file:///https:/parsefiles…

vs

https://parsefiles

so it could not compare them equal.

Hopefully my struggles will help other some day…

Here is the isDirty util function I use :

/**

  • . corrects an issue with dirty() that always return true when the object is new,
  • . so dirty can return true for a property that’s not returned by dirtyKeys()
  • @param parseObj
  • @param propertyName
  • @returns {*}
    */
    function isDirty(parseObj, propertyName) {
    if (parseObj.isNew()) {
    return parseObj.get(propertyName) != null; // dirty() always returns true there
    } else {
    return parseObj.dirty(propertyName);
    }
    }
1 Like

May I have one more question regarding passing the context from beforeSave to afterSave? I have two simplified triggers:

Parse.Cloud.beforeSave("Group", async ({object, context, log}) => {
    if (object.existed()) {
       if (object.dirty('lks')) {            
            //passing in context that lks was dirty as in afterSave, no key is dirty anymore
            context = { newLostKey: true };
            log.info(`beforeSave triggered for group: ${object.id}, lks dirty, context.newLostKey: ${context.newLostKey}`);             
        }
    }    
    return;   
});

I see in the dashboard log:

2021-09-24T07:16:24.143Z - beforeSave triggered for group: OsUpGGvgKG, lks dirty, context.newLostKey: true

When I try to check against this bool in afterSave it seems to be undefined:

Parse.Cloud.afterSave("Group", async ({object, original, context, log}) => {
    log.info(`afterSave triggered for group: ${object.id}, context.newLostKey: ${context.newLostKey}`);    
    return;
});

as it logs in the dashboard:

2021-09-24T07:16:24.152Z - afterSave triggered for group: OsUpGGvgKG, context.newLostKey: undefined

How should I pass it? I noticed that in the documentation is the context not in the trigger parameter Parse.Cloud.beforeSave("Group", async ({object, context, log}) but I don’t see it undefined in the first log entry

Many thanks!

EDIT

It seems that it indeed makes a difference. Adjusting it according to the example it pass the key:

Parse.Cloud.beforeSave("Group", async (request) => {
    const { object: object, log } = request;
    if (object.existed()) {
        if (object.dirty('lks')) {            
            //passing in context that lks was dirty as in afterSave, no key is dirty anymore
            request.context = { newLK: true };
            log.info(`beforeSave triggered for group: ${object.id}, lks dirty, context.newLK: ${request.context.newLK}`); 
        }
    }    
    return;   
});

I believe the old way it assigned the context as a new object and not part of request.

Parse.Cloud.afterSave("Group", async (request) => {
    const { object: object, original, user, context, log } = request;
    log.info(`afterSave triggered for group: ${object.id}, context.newLK: ${context.newLK}`);     
    return;
});

Returns correct true flag from context

2021-09-24T08:15:23.934Z - afterSave triggered for group: OsUpGGvgKG, context.newLK: true