Sails: Multiple calls for lifecycle callbacks

Created on 10 Mar 2015  路  13Comments  路  Source: balderdashy/sails

Hi
I've encountered strange behaviour of lifecycle callbacks.
Each of them are called multiple times during single PUT request.
For example, if I set following:

  beforeValidate: function (values, done) {
    console.log('Before Validate');
    done();
  },

then after a single put request my console shows me:

Before Validate
Before Validate
Before Validate
Before Validate

WTF?

needs documentation question

All 13 comments

Does the model you're acting on have associations?

yes, it has some

Does it have 3? :) beforeValidate will be called for every model that needs to be validated, which includes associations.

yep
Huh, what is the sense to call 'beforValidate' callback on model's associations? What data are passed to the callback?

When you pass an object with nested associations in it updates the parent and the child values. It was probably a mistake to add that feature but it's there in 0.10.x. So if you pass in User with 3 pets it will update the user and update the pets as well.

Well, I just found out that my 4 calls for beforeValidate cb are due to the presense of the following association:

language: {model: 'Language'},

And the Language model looks like this:

module.exports = {
  attributes: {
    key: {type: 'string', primaryKey: true, required: true, unique: true},
    title: {type: 'string', required: true},
    toJSON: function () {
      var obj = this.toObject();
      return {key: obj.key, title: obj.title};
    }
  },

  autoPK: false
};

If the body of my put request contain the language attr like this:

{
...
"language": {"key": "fr", "title": "French"},
...
}

then I actually have those 4 beforeValidate calls.
But if I pass language property in put request like this:

"language": "fr",

then I have only one beforeValidate cb call.

I decided to add to the main model and to all its assotiations the following beforeValidate callback:

beforeValidate: function (values, done) {
    console.log('BEFORE VALIDATE <Model Title>', _.keys(values));
}

And when I pass an object to the language property in PUT request, then I get:

BEFORE VALIDATE MAIN-MODEL [ 'createdAt',
  'desc',
  'id',
  'language',
  'state',
  'title',
  'updatedAt' ]
BEFORE VALIDATION LANGUAGE-MODEL [ 'key', 'title' ]
BEFORE VALIDATE MAIN-MODEL [ 'language' ]
BEFORE VALIDATE MAIN-MODEL [ 'title',
  'language',
  'desc',
  'author',
  'state',
  'createdAt',
  'updatedAt',
  'id' ]
BEFORE VALIDATE MAIN-MODEL [ 'title',
  'language',
  'desc',
  'author',
  'state',
  'createdAt',
  'updatedAt',
  'id' ]

Could you explain me

  1. Why does I have a single cb call in one case and 4 calls in another case?
  2. What is really happend when I pass an object instead of an id value as a language property in PUT request?

@particlebanana why was it a mistake ?. create/update with nested associations is a great feature that i haven't seen on any other framework yet...

I'm finding something very similar in beforeUpdate but it's causing major issues as for some reason one of the times it's triggered the model association field is passed a null value which then removes the association and I don't understand where this null value is coming from

Thanks for posting, @aantipov. I'm a repo bot-- nice to meet you!

It has been 60 days since there have been any updates or new comments on this page. If this issue has been resolved, feel free to disregard the rest of this message. On the other hand, if you are still waiting on a patch, please:

  • review our contribution guide to make sure this submission meets our criteria (only _verified bugs_ with documented features, please; no questions, commentary, or bug reports about undocumented features or unofficial plugins)
  • create a new issue with the latest information, including updated version details with error messages, failing tests, and a link back to the original issue. This allows GitHub to automatically create a back-reference for future visitors arriving from search engines.

Thanks so much for your help!

@particlebanana why was it a mistake ?. create/update with nested associations is a great feature that i haven't seen on any other framework yet...

@mkeremguc it is very handy, but also incredibly easy to accidentally trip up on (i.e. by accidentally passing in a deeply-nested dictionary). That said, we intend to continue to support it in its current capacity through the current release cycle-- we just need to lighten things up and deprecate it in the next major release of Waterline (it's become a huge maintenance burden that's largely fallen on @particlebanana's shoulders even though he's not currently using it in a production app.) If anyone using it in production would like to help keep that feature alive in Waterline v1.0, please reach out to us on Twitter.

Posted a comment here https://github.com/balderdashy/waterline/issues/751#issuecomment-179931398

Would need to know which call is the first one. Is that possible?

I found the solution, but it need to be verified carefully, but I am hurry to share it with you guys - not beat me if something wrong.
I have several update requests, because my model has associations.

I've resolved it by puting verification variable. If I want to update some secure data (e.g. payment status - I pass in update data additional verification attribute), if I don't pass - data not updating - remove from updating (leave like it is).

```javascript
....
beforeUpdate: function(values, cb) {
// reset security values if request comes from user - not API itself
if(values.payment_status && values.permitCode !== sails.config.custom_config.permit_code) delete values.payment_status; // prevent create payment status manually

cb();

},
....
````

The other solution is to initialize variable after first pass and to verify it in the next passes. Variable should be global. My code is next:
```javascript
....
beforeUpdate: function(values, cb) {
if(typeof dirtyData == 'undefined') {
//make some operations only in first pass
}
dirtyData = true;
cb();
},
....
````
and do not forget to unset it in building response in badRequest.js and ok.js because, in case the next request, it will already be initialized:

```javascript
module.exports = function badRequest(data, options) {
....
delete dirtyData;
....
};
````

```javascript
module.exports = function sendOK (data, options) {
....
delete dirtyData;
....
};
````

after first pass variable "dirtyData" will be initialized

Was this page helpful?
0 / 5 - 0 ratings

Related issues

radoslavpetranov picture radoslavpetranov  路  4Comments

anissen picture anissen  路  3Comments

edy picture edy  路  4Comments

visitsb picture visitsb  路  4Comments

svmn picture svmn  路  4Comments