Sails: beforeValidate called 3 times on update with association

Created on 3 Dec 2014  路  10Comments  路  Source: balderdashy/sails

I have a user and role model which are associated:

User:

types: {
    password: function(password) {
      return _.isEmpty(password) || password === this.passwordconf;
    }
  },

  attributes: { 
        username: { type: 'string' }, 
        password: { type: 'string', required: true, password: true, minLength: 6 }, 
        firstname: { type: 'string', required: true }, 
        surname: { type: 'string', required: true }, 
        email: { type: 'string', unique: true, required: true, email: true }, 
        cell: { type: 'string', required: true }, 
        loginAt: { type: 'datetime' },

        role: { model: 'userrole' },
        client: { model: 'client' },
        avatar: { model: 'upload' },

        toJSON: function() {
            var obj = this.toObject();
            delete obj.password;
            return obj;
        }
  }

User role:

attributes: {
        role: { type: 'string' }, 
        role_description: { type: 'string' },
        users: {
            collection: 'user',
            via: 'role'
        }
  }

Submit without associated data populated:

//submitted data: 
{
  "firstname": "John"
}

Console output:

Requested ::  PUT /api/user/1
beforeValidate { firstname: 'John' }
// this works just fine

Submit with associated data populated

// submitted data: 
{
  "firstname": "John",
  "role": { "role": "admin", "role_description": "Full acccess.", "id": 1 }
}

Console output:

Requested ::  PUT /api/user/1
beforeValidate { firstname: 'John', role: 1 }
beforeValidate { role: null }
beforeValidate { username: '[email protected]',
  password: '$2a$10$LWlmpauQu06FjY/dAjwV3udCWCiUvfT9p7fpOgZs3lcPmfyPaytPW',
  firstname: 'John',
  surname: 'Doe',
  email: '[email protected]',
  cell: '1234567890',
  loginAt: Wed Dec 03 2014 08:57:54 GMT+0200 (South Africa Standard Time),
  id: 1,
  createdAt: Thu Oct 30 2014 22:23:48 GMT+0200 (South Africa Standard Time),
  updatedAt: Wed Dec 03 2014 11:05:19 GMT+0200 (South Africa Standard Time),
  role: 1,
  client: 3,
  avatar: null }

If submitted with associated data some things go wrong. As you can see beforeValidate executes 3 times for the request that has associated data in it. The first one makes sense, the second and third one does not.

So one of things that happens at the third beforeValidate is that it seems to get the actual row data from the table and then extend it with the submitted data and use that to update the row. Because I'm testing for a password to have an equal passwordconf this gives a validation error that the passwords don't match, which it shouldn't since I didn't submit the password to be updated.

To get around that I had to add the following to my User model beforeValidate:

// delete the password if it's an update and it's not set by submit data
if (values.id || values.password.indexOf('$2a$10') !== -1) delete values.password;

Then that solves that problem, but then I get:

Details:  Error: ER_NO_REFERENCED_ROW_: Cannot add or update a child row: a fore
ign key constraint fails (`membejru_test`.`user`, CONSTRAINT `user_role
` FOREIGN KEY (`role`) REFERENCES `userrole` (`id`) ON DELETE NO ACTION ON UPDAT
E NO ACTION)

Adding the following to my beforeUpdate solves that:

if (!values.role) delete values.role;

So I really had to jump through a few hoops to get it to work but it doesn't seem like this is supposed to be an issue in the first place?

Am I doing something wrong or is this a bug?

Most helpful comment

I faced similar issue in beforeUpdate callback : I am updating model with associated data.
The callback runs 3 times. I need to do some preprocessing before updating.
Because of this multiple callbacks , I am unable to do it.
Has anybody found solution to it ?

All 10 comments

I just ran into something similar. Ever since I started using collections and models, suddenly beforeUpdate runs multiple times. beforeCreate does not. Only beforeUpdate...is it possible it is trying to run updates on the related tables?

I just found that in the valuesParser in nesterOperations util, returning associations before the the loop (right after definition) fixes the issue and stops running the validators 3 times, but of course then doesnt run any nested operations. I'll keep digging.

So the reason why this is a problem for me is there is no way to tell what iteration of the validator I am on,

My user table, uses bcrypt on update when data exists in the password field.

However the 2nd iteration, as well as the third, actually pull in the password hash, as if it had requeries before it runs the 2nd and 3rd.

This causes a huge problem because I can't check for the absence of the password provided to update. I spent about 4 hours trying to track down this issue.

The only workaround I was able to find was to pass some kidn of bogus data attribute with the record, so that the validator script will jsut return the cb() if it notices it not there on the 2nd and third pass. IF your using schema, then the the data gets tosses, and the 2nd iteration does not have it included. If you are using schemaless, you may end up with a problem and may need to make sure the function is in the model, create a hash key on a global var in the file, and then remove it if it passes the first if check.

WHAT a PAIN!

I have the same problem which causes my validation step to fail :cry:

When I create a new model I get the following lifecycle callbacks:

  • beforeValidate
  • afterValidation
  • beforeCreate
  • beforeValidate (my code fails unique ID check fail because a model with that ID already exists)

I figured out whats going on.

So it has to do with me being a derp and pushing all my relational record data all at once and not caring how big an object I create or if some of those objects don't have populated data in them.

I solved this by creating a policy to delete keys with array values, and replace objects with id's with just the id

http://pastebin.com/B6w0mG3Y

You can mold this to how you like, I always pass in my main data in the record field.

It would be great however to disable association updates on a per model level as a model setting. This would allow us to use populate as needed but avoid update issues.

@EmileSpecs, @anissen, what is the status of this? Can you try with latest version of waterline? Thanks!

Thanks for posting, @EmileSpecs. 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!

This should not be closed...

I'm running into a similar problem in beforeValidate callback : I need to check associated data, so I query the database.

But the callback runs 3 times, because of associations.

Problem is, on the 2nd and 3rd calls, the query user.findOne(id).populate("pets") will return a user with an empty array as a pets key. At this stage, the association table doesn't contain records of the association.

So I simply cannot validate the input.

I faced similar issue in beforeUpdate callback : I am updating model with associated data.
The callback runs 3 times. I need to do some preprocessing before updating.
Because of this multiple callbacks , I am unable to do it.
Has anybody found solution to it ?

Is this ever going to be fixed?

Was this page helpful?
0 / 5 - 0 ratings