How to query objects in the CloudCode beforeSave?
Asked Answered
G

5

1

I'm trying to compare a new object with the original using CloudCode beforeSave function. I need to compare a field sent in the update with the existing value. The problem is that I can't fetch the object correctly. When I run the query I always get the value from the sent object.

UPDATE: I tried a different approach and could get the old register ( the one already saved in parse). But the new one, sent in the request, was overridden by the old one. WHAT?! Another issue is that, even thought the code sent a response.success(), the update wasn't saved. I believe that I'm missing something pretty obvious here. Or I'm facing a bug or something...

NEW APPROACH

Parse.Cloud.beforeSave('Tasks', function(request, response) {
  if ( !request.object.isNew() )
  {
    var Task = Parse.Object.extend("Tasks");
    var newTask = request.object;
    var oldTask = new Task();
    oldTask.set("objectId", request.object.id);
    oldTask.fetch()
        .then( function( oldTask )
    {
        console.log(">>>>>> Old Task: " + oldTask.get("name") + " version: " + oldTask.get("version"));
        console.log("<<<<<< New Task: " + newTask.get("name") + " version: " + newTask.get("version"));
        response.success();
    }, function( error ) {
            response.error( error.message );
        }
    );
    }
});

OBJ SENT {"name":"LLL", "version":333}

LOG

 I2015-10-02T22:04:07.778Z]v175 before_save triggered for Tasks for user tAQf1nCWuz:
 Input: {"original":{"createdAt":"2015-10-02T17:47:34.143Z","name":"GGG","objectId":"VlJdk34b2A","updatedAt":"2015-10-02T21:57:37.765Z","version":111},"update":{"name":"LLL","version":333}}
 Result: Update changed to {}
 I2015-10-02T22:04:07.969Z]>>>>>> Old Task: GGG version: 111
 I2015-10-02T22:04:07.970Z]<<<<<< New Task: GGG version: 111

NOTE: I'm testing the login via cURL and in the parse console.

CloudCode beforeSave

Parse.Cloud.beforeSave("Tasks", function( request, response) {
  var query = new Parse.Query("Tasks");
  query.get(request.object.id)
    .then(function (oldObj) {
        console.log("-------- OLD Task: " + oldObj.get("name") + " v: " + oldObj.get("version"));
        console.log("-------- NEW Task: " + request.object.get("name") + " v: " + request.object.get("version"));
    }).then(function () {
        response.success();
    }, function ( error) {
        response.error(error.message);
    }
  );
});

cURL request

curl -X PUT \
-H "Content-Type: application/json" \
-H "X-Parse-Application-Id: xxxxx" \
-H "X-Parse-REST-API-Key: xxxxx" \
-H "X-Parse-Session-Token: xxxx" \
-d "{\"name\":\"NEW_VALUE\", \"version\":9999}" \
https://api.parse.com/1/classes/Tasks/VlJdk34b2A

JSON Response

"updatedAt": "2015-10-02T19:45:47.104Z"

LOG The log prints the original and the new value, but I don't know how to access it either.

I2015-10-02T19:57:08.603Z]v160 before_save triggered for Tasks for user tAQf1nCWuz:
Input: {"original":{"createdAt":"2015-10-02T17:47:34.143Z","name":"OLD_VALUE","objectId":"VlJdk34b2A","updatedAt":"2015-10-02T19:45:47.104Z","version":0},"update":{"name":"NEW_VALUE","version":9999}}
Result: Update changed to {"name":"NEW_VALUE","version":9999}
I2015-10-02T19:57:08.901Z]-------- OLD Task: NEW_VALUE v: 9999
I2015-10-02T19:57:08.902Z]-------- NEW Task: NEW_VALUE v: 9999
Gast answered 2/10, 2015 at 20:0 Comment(6)
I'm not sure that you can do this. What is the purpose you are doing it? Maybe we can find a different solution.Ansell
I need to check the version of the register and compare with the version sent in the request.Gast
That is very strange, I really don't understand why your version labeled 'NEW APPROACH' doesn't work... it should. I see your note about the Parse.Object.disableSingeInstance but that is the first I have heard of that. Seems like that is highly inefficient as you have to fetch the object twice. I would reach out to Hector via Twitter or FB and ask about this.Savor
@Savor I totally agree with you. In a production scenario my solution wouldn't be advisable. As a matter o fact, this problem is so crazy that I'm thinking about drop Parse completely. If this Hector guy is someone from Parse.com, can you please gimme his contact? Maybe he can save me from this hell.Gast
@Savor I already have his contact. Tks for the tip. Will try to contact him nowGast
Let me know what you hear. Sorry I was slow in replying, I was away from 'the bits' for a long weekend.Savor
G
1

After a lot test and error I could figure out what was going on.

Turn out that Parse is merging any objects with the same class and id into one instance. That was the reason why I always had either the object registered in DB or the one sent by the user. I honestly can't make sense of such behavior, but anyway...

The Parse javascript sdk offers an method called Parse.Object.disableSingeInstance link that disables this "feature". But, once the method is called, all object already defined are undefined. That includes the sent object. Witch means that you can't neither save the sent object for a later reference.

The only option was to save the key and values of the sent obj and recreate it later. So, I needed to capture the request before calling disableSingleInstance, transform it in a JSON, then disable single instance, fetch the object saved in DB and recreate the sent object using the JSON saved.

Its not pretty and definitely isn't the most efficient code, but I couldn't find any other way. If someone out there have another approach, by all means tell me.

Parse.Cloud.beforeSave('Tasks', function(request, response) {
  if ( !request.object.isNew() ) {
    var id = request.object.id;
    var jsonReq;
    var Task = Parse.Object.extend("Tasks");
    var newTask = new Task;
    var oldTask = new Task;

    // getting new Obj
    var queryNewTask = new Parse.Query(Task);
    queryNewTask.get(id)
        .then(function (result) {
            newTask = result;

            // Saving values as a JSON to later reference
            jsonReq = result.toJSON();

            // Disable the merge of obj w/same class and id
            // It will also undefine all Parse objects,
            // including the one sent in the request
            Parse.Object.disableSingleInstance();

            // getting object saved in DB
            oldTask.set("objectId", id);
            return oldTask.fetch();
        }).then(function (result) {
            oldTask = result;

            // Recreating new Task sent
            for ( key in jsonReq ) {
                newTask.set( key, jsonReq[key]);
            }

            // Do your job here
        }, function (error) {
            response.error( error.message );
        }
    );
  }
});
Gast answered 6/10, 2015 at 2:57 Comment(0)
A
0

Rather than performing a query, you can see the modified attributes by checking which keys are dirty, meaning they have been changed but not saved yet.

The JS SDK includes dirtyKeys(), which returns the keys that have been changed. Try this out.

var attributes = request.object.attributes; 
var changedAttributes = new Array(); 

for(var attribute in attributes) {

    if(object.dirty(attribute)) { 
        changedAttributes.push(attribute);
        // object.get(attribute) is changed and the key is pushed to the array
    }
}

For clarification, to get the original attribute's value, you will have to call get() to load those pre-save values. It should be noted that this will count as another API request.

Androcles answered 2/10, 2015 at 20:31 Comment(10)
According to parse blog dirtyKey is useful to compare with field was modified, right? But I need the values of a specific field. I need to compare the value of a field registered with the same field value in the request. There is a way to do that with dirtyKey?Gast
Just updated my post to clarify. You are correct that dirtyKey can only be used to tell which attributes were changed, but not the values of the keys beforehand. Those would have to be fetched using another request. Parse does not expose previousAttributes() or previous("columnName") in the JS SDK.Androcles
Right, but this is my problem. When I try to GET the value stored in the DB inside the beforeSave function, I receive the value sent by the user and not the one saved in parse. Suppose that class X have a value 100 stored and the user in sending 900. When I try to get the class X, using the objId sent by the user I've got 900. The exact same value that was sent by the user? Check it out my query in the question.Gast
I believe the problem is that query is executed asynchronously. By the time the query promise is fulfilled the modified object has already been saved. Move your response.success() below the two logging statements. By calling response.success(), you're signaling that the object save can proceedAndrocles
By my understanding the "response" is been called in another promise, hence, only triggers after the console prints. Right? I'm new to the promise thing, but it looks right to me. Take a look in the code and correct me if I'm wrong. Tks =)Gast
.then(function (oldObj) { console.log ... }).then(function () { response.success();Gast
When in doubt add more console.log statements so you can trace the execution. You're right that the serial promise chain should execute after the console prints, but I can't see any reason why you have a chain in the first place. You could do everything within the same promise as is.Androcles
Also, this may help as well #25082762Androcles
Well, I created the chain because I thought that the was the async call causing the problem, just as you said. But, Doesn't helped! lolGast
Promises still execute asynchronously, it's just another JS convention for handling callbacksAndrocles
A
0

If I were you, I would pass in the old value as a parameter to the cloud function so that you can access it under request.params.(name of parameter). I don't believe that there is another way to get the old value. An old SO question said that you can use .get(), but you're claiming that that is not working. Unless you actually already had 9999 in the version...

edit - I guess beforeSave isn't called like a normal function... so create an "update version" function that passes in the current Task and the version you're trying to update to, perhaps?

Ansell answered 2/10, 2015 at 20:31 Comment(3)
I can't do that. When I say OLD, I'm referring to the register that is saved in the DB. I need to compare the new registerGast
Parse does not allow you to pass params to a beforeSave hook. Source: parse.com/questions/how-to-access-request-params-in-beforesaveAndrocles
@Androcles see the edit I had made. TinMegali , You'll have to pass in the current value and the new value you're trying to change to to a separate function, do the comparison, and then save if they meet your requirements.Ansell
F
0

Hey this worked perfectly for me :

var dirtyKeys = request.object.dirtyKeys();
var query = new Parse.Query("Question");
var clonedData = null;
        query.equalTo("objectId", request.object.id);
        query.find().then(function(data){
            var clonedPatch = request.object.toJSON();
            clonedData = data[0];
            clonedData = clonedData.toJSON();
            console.log("this is the data : ", clonedData, clonedPatch, dirtyKeys);
            response.success();
        }).then(null, function(err){
            console.log("the error is : ", err);
        });
Fromm answered 17/9, 2016 at 8:31 Comment(0)
B
0

For those coming to this thread in 2021-ish, if you have the server data loaded in the client SDK before you save, you can resolve this issue by passing that server data from the client SDK in the context option of the save() function and then use it in the beforeSave afterSave cloud functions.

// eg JS client sdk
const options  = { 
  context: {
    before: doc._getServerData() // object data, as loaded
  } 
}
doc.save(null, options)
// #beforeSave cloud fn
Parse.Cloud.beforeSave(className, async (request) => {
  const { before } = request.context
  // ... do something with before ... 
})

Caveat: this wouldn't help you if you didn't have the attributes loaded in the _getServerData() function in the client

Second Caveat: parse will not handle (un)serialization for you in your cloud function, eg:

{
  before: { // < posted as context
    status: {
      is: 'atRisk',
      comment: 'Its all good now!',
      at: '2021-04-09T15:39:04.907Z', // string
      by: [Object] // pojo
    }
  },
  after: {
    status: { // < posted as doc's save data
      is: 'atRisk',
      comment: 'Its all good now!',
      at: 2021-04-09T15:39:04.907Z, // instanceOf Date
      by: [ParseUser] // instanceOf ParseUser
    }
  }
}
Blowout answered 14/4, 2021 at 19:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.