{"action":"created","issue":{"url":"https://api.github.com/repos/balderdashy/waterline/issues/1122","repository_url":"https://api.github.com/repos/balderdashy/waterline","labels_url":"https://api.github.com/repos/balderdashy/waterline/issues/1122/labels{/name}","comments_url":"https://api.github.com/repos/balderdashy/waterline/issues/1122/comments","events_url":"https://api.github.com/repos/balderdashy/waterline/issues/1122/events","html_url":"https://github.com/balderdashy/waterline/pull/1122","id":101213639,"number":1122,"title":"Call beforeUpdate, beforeDestroy, beforeValidate callbacks with criteria","user":{"login":"mmiller42","id":11927375,"avatar_url":"https://avatars.githubusercontent.com/u/11927375?v=3","gravatar_id":"","url":"https://api.github.com/users/mmiller42","html_url":"https://github.com/mmiller42","followers_url":"https://api.github.com/users/mmiller42/followers","following_url":"https://api.github.com/users/mmiller42/following{/other_user}","gists_url":"https://api.github.com/users/mmiller42/gists{/gist_id}","starred_url":"https://api.github.com/users/mmiller42/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mmiller42/subscriptions","organizations_url":"https://api.github.com/users/mmiller42/orgs","repos_url":"https://api.github.com/users/mmiller42/repos","events_url":"https://api.github.com/users/mmiller42/events{/privacy}","received_events_url":"https://api.github.com/users/mmiller42/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/balderdashy/waterline/labels/feature","name":"feature","color":"c7def8"}],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":14,"created_at":"2015-08-15T22:11:27Z","updated_at":"2016-03-16T20:11:06Z","closed_at":"2016-03-16T16:36:46Z","pull_request":{"url":"https://api.github.com/repos/balderdashy/waterline/pulls/1122","html_url":"https://github.com/balderdashy/waterline/pull/1122","diff_url":"https://github.com/balderdashy/waterline/pull/1122.diff","patch_url":"https://github.com/balderdashy/waterline/pull/1122.patch"},"body":"Pass criteria to the beforeUpdate, beforeDestroy, and beforeValidate callbacks, before the `callback` param.\r\n\r\nIn beforeValidate callbacks, criteria is `null` for create operations.\r\n\r\nServes as a solution to issue #1004.\r\n\r\n*NOTE:* For backwards compatibility, it now checks if the beforeValidate or beforeCreate callback(s) only accept 3 arguments or beforeDestroy accepts only 2 and if so, passes the callback as the last argument and omits criteria.\r\n\r\nExample usage and explanation:\r\n\r\n```js\r\n// User.js\r\nmodule.exports = {\r\n  attributes: {\r\n    email: { type: 'email', required: true, unique: true },\r\n    password: { type: 'string', required: true }\r\n  },\r\n\r\n  /**\r\n   * Hashes the password if it is provided\r\n   * @param {Object} values The data submitted via the API\r\n   * @param {Function} next The callback to invoke when done, with optional error\r\n   */\r\n  beforeCreate: function (values, next) {\r\n    if (values.password === undefined) return next();\r\n    hashPassword(values.password, function (err, hash) {\r\n      if (err) return next(err);\r\n      values.password = hash;\r\n      next();\r\n    });\r\n  },\r\n\r\n  /**\r\n   * Hashes the password if it is provided, but only if the provided\r\n   * password is not the user's existing password hash (safeguards\r\n   * against the record.save() bug)\r\n   * @param {Object} values The values to update\r\n   * @param {Object} criteria The criteria to match the record(s) to update\r\n   * @param {Function} next The callback to invoke when done, with optional error\r\n   */\r\n  beforeUpdate: function (values, criteria, next) {\r\n    if (values.password === undefined) return next();\r\n\r\n    // Look up user and only change password if values.password isn't the hash\r\n    User.findOne(criteria).exec(function (err, user) {\r\n      if (err) return next(err);\r\n      if (!user) return next(new Error('User to update does not seem to exist!'));\r\n\r\n      if (values.password === user.password) return next();\r\n\r\n      hashPassword(values.password, function (err, hash) {\r\n        if (err) return next(err);\r\n        values.password = hash;\r\n        next();\r\n      });\r\n    });\r\n  },\r\n\r\n  /**\r\n   * Validates the password against constraints, but only if the user does\r\n   * not exist, or the provided password is not the user's existing password\r\n   * hash (safeguards against the record.save() bug)\r\n   * @param {Object} values The values to update or insert\r\n   * @param {Object|null} criteria The criteria to match the record(s) to\r\n   *   update, or null if creating a new record\r\n   * @param {Function} next The callback to invoke when done, with optional error\r\n   */\r\n  beforeValidate: function (values, criteria, next) {\r\n    if (values.password === undefined) return next();\r\n    if (!criteria) { // create\r\n      return validatePassword(values.password, next);\r\n    } else { // update\r\n      // Look up user to see if values.password is actually just the existing hash\r\n      User.findOne(criteria).exec(function (err, user) {\r\n        if (err) return next(err);\r\n        if (!user) return next(new Error('User to update does not seem to exist!'));\r\n\r\n        if (user.password === values.password) return next();\r\n\r\n        return validatePassword(values.password, next);\r\n    }\r\n  }\r\n};\r\n\r\n/**\r\n * Returns a hash with embedded salt given a string\r\n * @param {string} password The string to hash\r\n * @param {Function} next The callback to invoke with null or hash error and the hash\r\n */\r\nfunction hashPassword (password, next) {\r\n  require('bcrypt').hash(password, 10, next);\r\n}\r\n\r\n/**\r\n * Validates a password against the constraints\r\n * @param {string} password The password to validate\r\n * @param {Function} next The callback to invoke with null or the validation error\r\n */\r\nfunction validatePassword (password, next) {\r\n  if (password.length < 5 || password.length > 20)\r\n    return next(new Error('Password should be 5-20 characters.'));\r\n  next(null);\r\n}"},"comment":{"url":"https://api.github.com/repos/balderdashy/waterline/issues/comments/197525551","html_url":"https://github.com/balderdashy/waterline/pull/1122#issuecomment-197525551","issue_url":"https://api.github.com/repos/balderdashy/waterline/issues/1122","id":197525551,"user":{"login":"particlebanana","id":370260,"avatar_url":"https://avatars.githubusercontent.com/u/370260?v=3","gravatar_id":"","url":"https://api.github.com/users/particlebanana","html_url":"https://github.com/particlebanana","followers_url":"https://api.github.com/users/particlebanana/followers","following_url":"https://api.github.com/users/particlebanana/following{/other_user}","gists_url":"https://api.github.com/users/particlebanana/gists{/gist_id}","starred_url":"https://api.github.com/users/particlebanana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/particlebanana/subscriptions","organizations_url":"https://api.github.com/users/particlebanana/orgs","repos_url":"https://api.github.com/users/particlebanana/repos","events_url":"https://api.github.com/users/particlebanana/events{/privacy}","received_events_url":"https://api.github.com/users/particlebanana/received_events","type":"User","site_admin":false},"created_at":"2016-03-16T20:11:06Z","updated_at":"2016-03-16T20:11:06Z","body":"Hmm I didn't close this, I deleted the outdated branch that it was a PR against though."}}