Waterline/SailsJs 防止模型的嵌套(关系)保存

Waterline/SailsJs prevent nested (relationship) save of model

我有一个模型 "User",它与 "Subject" 具有多对一关系。

User.js

attributes: {
    subject: { model: 'subject' },
}

Subject.js

attributes: {
    name: { type: 'string', unique: true, required: true },
}

当我为用户“/user”调用蓝图创建函数并传入数据时:

{
    "name":"Test",
    "subject":{"name":"Do Not Allow"}
}

它创建了用户,也创建了主题。但是我不想允许创建主题,我只想能够附加一个现有的主题。例如,我希望它拒绝使用上述数据创建的主题,但允许使用以下数据附加主题。

{
    "name":"Test",
    "subject":1
}

我尝试添加一个策略(如下所示),但这只会阻止使用 URL“/subject”创建主题,而不是上面显示的嵌套创建。

'SubjectController':{
    'create':false
}

编辑 为了帮助理解这里发生了什么,这是它正在经历的生命周期过程:

Before Validation of Subject
After Validation of Subject
Before Creating Subject
After Creating Subject
Before Validation of User
After Validation of User
Before Creating User
Before Validation of User
After Validation of User
After Creating User

如您所见,它甚至在验证或创建用户之前就先验证和创建主题。

您可以为主题创建自定义类型,并在模型中添加您的逻辑。我不是 100% 确定我理解附加有时部分,但也许这会有所帮助:

models/User.js

module.exports = {

    schema: true,

    attributes: {
        name: {
            type: 'string'
        },

        subject: {
            type: 'json',
            myValidation: true
        }
    },

    types: {
        myValidation: function(value) {
            // add here any kind of logic...

            // for example... reject if someone passed name key
            return !value.name;
        }
    }
};

您可以在页面底部的 http://sailsjs.org/documentation/concepts/models-and-orm/validations 此处找到更多信息。

如果我完全没有抓住要点...第二个选项是像这样向您的模型添加 beforeCreate 和 beforeUpdate 生命周期回调:

models/User.js

 module.exports = {

    schema: true,

    attributes: {
        name: {
            type: 'string'
        },

        subject: {
            type: 'json'
        }
    },

    beforeCreate: function (values, cb) {

        // for example... reject creating of subject if anything else then value of 1
        if (values.subject && values.subject !== 1) return cb('make error obj...');

        cb();
    },


    beforeUpdate: function (values, cb) {
        // here you can add any kind of logic to check existing user or current update values that are going to be updated
        // and allow it or not

        return cb();
    }
};

通过使用它,您可以使用一种逻辑进行创建,另一种逻辑进行更新...等等...

您可以在此处找到更多信息:http://sailsjs.org/documentation/concepts/models-and-orm/lifecycle-callbacks

编辑

意识到你在关系方面有问题,在上面的例子中我认为你正在处理类型 json...

module.exports = {

    schema: true,

    attributes: {
        name: {
            type: 'string'
        },

        subject: {
            model: 'subject'
        }
    },

    beforeValidate: function (values, cb) {

        // subject is not sent at all, so we just go to next lifecycle
        if (!values.subject) return cb();

        // before we update or create... we will check if subject by id exists...
        Subject.findOne(values.subject).exec(function (err, subject) {
            // subject is not existing, return an error
            if (err || !subject) return cb(err || 'no subject');

            //

            // you can also remove subject key instead of sending error like this:
            // delete values.subject;

            //

            // subject is existing... continue with update
            cb();
        });
    }
};

您应该传递主题 ID(例如 1)而不是包含主题名称的对象(例如 { name: 'Hello, World!' }),因为它不一定是唯一的。

如果它是唯一的,您应该用它在 beforeValidate 中的 id 替换对象。例如。

// User.js

module.exports = {

  ...

  beforeValidate: function (users, callback) {
    // users = [{
    //   "name":"Test",
    //   "subject":{"name":"Do Not Allow"}
    // }]

    async.each(users, function replaceSubject(user, next) {
      var where = {};
      if (_.isObject(user.subject) && _.isString(user.subject.name)) {
        where.name = user.subject.name;
      } else if(_.isInteger(user.subject)) {
        where.id = user.subject;
      } else {
        return next();
      }

      // Check the existence of the subject
      Subject
        .findOne(where)
        .exec(function (err, subject) {
          if (err) return next(err);

          // Create a user without a subject if it does not exist
          user.subject = subject? subject.id : null;
          next();
      });
    }, callback);

    // users = [{
    //   "name":"Test",
    //   "subject":1
    // }]
  }

};

您想在调用蓝图创建路由时避免创建关联对象

创建一个策略(我将其命名为 checkSubjectAndHydrate)并将其添加到 policies.js 文件中:

// checkSubjectAndHydrate.js
module.exports = function (req, res, next) {

  // We can create a user without a subject
  if (_.isUndefined(req.body.subject)) {
    return next();
  }

  // Check that the subject exists
  Subject
    .findOne(req.body.subject)
    .exec(function (err, subject) {
      if (err) return next(err);

      // The subject does not exist, send an error message
      if (!subject) return res.forbidden('You are not allowed to do that');

      // The subject does exist, replace the body param with its id
      req.body.subject = subject.id;

      return next();
  });

};

// policies.js
module.exports.policies = {

  UserController: {
    create: 'checkSubjectAndHydrate',
    update: 'checkSubjectAndHydrate',
  }

};