Node.js sails.js嵌套模型

Node.js sails.js嵌套模型,node.js,sails.js,waterline,Node.js,Sails.js,Waterline,在sails.js 0.10中,我尝试执行以下操作 // user.js module.exports = { attributes: { uuid: { type: 'string', primaryKey: true, required: true } , profile: { firstname: 'string', lastname: 'string', b

在sails.js 0.10中,我尝试执行以下操作

// user.js
module.exports = {
  attributes: {

    uuid: {
        type: 'string',
        primaryKey: true,
        required: true
     } ,
     profile: {

        firstname: 'string',
        lastname: 'string',
        birthdate: 'date',
        required: true
     }
  }
};
尝试创建用户时出错,sailsJS无法识别“profile”属性。我不确定sails是否支持嵌套的JSON结构,如果支持,我也不确定如何构造它

error: Sent 500 ("Server Error") response
error: Error: Unknown rule: firstname
我试过以下方法,但也失败了

// user.js
module.exports = {
  attributes: {

    uuid: {
        type: 'string',
        primaryKey: true,
        required: true
     } ,
     profile: {

        firstname: {type: 'string'},
        lastname: {type: 'string'},
        birthdate: 'date',
        required: true
     }
  }
};

我知道sailsJS 0.10中有一个名为“JSON”的属性,但不确定该属性如何适合此模块。

Waterline不支持定义嵌套模式,但您可以使用
JSON
类型在模型中存储嵌入对象。那么,你会做:

profile: {
    type: 'json',
    required: true
}
然后您可以创建如下用户实例:

User.create({profile: {firstName: 'John', lastName: 'Doe'}})
区别在于,
firstName
lastName
字段不会被验证。如果要验证嵌入的
profile
对象的架构是否与所需匹配,则必须在模型类中实现
beforevaluate()
生命周期回调:

attributes: {},
beforeValidate: function(values, cb) {
    // If a profile is being saved to the user...
    if (values.profile) {
       // Validate that the values.profile data matches your desired schema,
       // and if not call cb('profile is no good');
       // otherwise call cb();
    }
}
下面我创建了一些额外的逻辑来帮助解决这个问题

由于我不止一次使用这种“嵌套模型”,所以我为那些
beforeValidade()
lifecycle回调创建了一个带有工厂的服务


下面是它的样子:

var _ = sails.util;
var WLValidationError = require('../../node_modules/sails/node_modules/waterline/lib/waterline/error/WLValidationError.js');


function validationError(invalidAttributes, status, message) {
  // Wrapper to helo with validation Errors
  return new WLValidationError({
      invalidAttributes: invalidAttributes,
      status: status,
      message: message
    }
  );
}


function fulfillJSON(attrValues, innerName, innerAttr) {
  // Helper to get default values into the JSON
  // Work with recurrency for arrays
  if (_.isArray(attrValues)) {
    return attrValues.map((attrVal) => {
      return fulfillJSON(attrVal, innerName, innerAttr);
    });
  }

  innerValue = attrValues[innerName];
  // Treat empty values
  if (innerValue == null) {
    // Check to see if it's required
    if (!innerAttr.required) {
      // If not required, try to set the defult value
      innerValue = innerAttr.defaultsTo;
    }
  }

  return attrValues;
}


function validateJSON(attrValues, innerName, innerAttr, invalidAttr, index) {
  // Helper to get error messages if it's not valid
  // Work with recurrency for arrays
  if (_.isArray(attrValues)) {
    invalidAttr = invalidAttr || {};
    _.each(attrValues, (attrVal) => {
      invalidAttr = validateJSON(attrVal, innerName, innerAttr, invalidAttr, attrValues.indexOf(attrVal));
    });
    return invalidAttr;
  }

  invalidMessage = "";

  innerValue = attrValues[innerName];
  // Treat empty values
  if (innerValue == null) {
    // Check to see if it's required
    if (innerAttr.required) {
      invalidMessage += '\n`' + innerName + '` is required!'
    };
  } else
  // Check if it has the right data type
  if (innerAttr.type) {
    if (typeof innerValue !== innerAttr.type) {
      invalidMessage += '\n`' + innerName + '` should be of type `' + innerAttr.type + '`!'
    };
  }

  if (invalidMessage != "") {
    invalidAttr = invalidAttr || {};
    innerInvalid = invalidAttr[innerName];

    if (innerInvalid != null && !_.isArray(innerInvalid)) {
      // Create an array if this attribute already have errors
      innerInvalid = [innerInvalid]
    };
    if (_.isArray(innerInvalid)) {
      // If it's an array, push new errors
      innerInvalid.push({
        index: index,
        field: innerName,
        value: innerValue,
        message: invalidMessage
      });
    } else {
      // If it's the first error, just create the object
      innerInvalid = {
        index: index,
        field: innerName,
        value: innerValue,
        message: invalidMessage
      };
    }

    invalidAttr[innerName] = innerInvalid;
  }
  return invalidAttr;
}


module.exports = {
  validateJSONFactory: function(jsonAttrs) {

    return function(values, cb) {
      // Object to store possible errors
      var invalidAttributes;

      // Go through each attibue trying to find json
      _.each(jsonAttrs, (attrSpecs, attrName) => {
        // Object to store specific attribute errors
        var invalidAttr;

        // Get the values to be validated
        attrValues = values[attrName]
        try {
          attrValues = JSON.parse(attrValues);
        } catch(e) {
          // console.log("Couldn't parse object, ignoring for now!")
          invalidAttributes[attrName] = {
            message: "Couldn't parse object!"
          };
          return false;
        }

        // Check if the specs are those of arrays
        if (_.isArray(attrSpecs)) {
          attrSpecs = attrSpecs[0];
          // Treat should be arrays
          if (!_.isArray(attrValues)) {
            attrValues = [attrValues];
          }
        }

        //Go through the specs in order to do some validation
        _.each(attrSpecs, (innerAttr, innerName) => {
          attrValues = fulfillJSON(attrValues, innerName, innerAttr);
          invalidAttr = validateJSON(attrValues, innerName, innerAttr, invalidAttr);
        });

        // Overload initial value, give back as string, the same way we got it!
        // values[attrName] = JSON.stringify(attrValues)
        values[attrName] = attrValues;

        // Make errors available outside
        if (invalidAttr != null){
          invalidAttributes = invalidAttributes || {};
          invalidAttributes[attrName] = invalidAttr;
        }

      }) // </each>

      if (invalidAttributes != null) {
        return cb(validationError(invalidAttributes));
      }

      return cb();

    } // </return function>
  } // </fulfillJSONFactory>
}  // </module.exports>


我知道它还不完美,也许我应该把它做成一个钩子,但我仍然不知道最好的方法是什么。

谢谢!那真的很有帮助
const jsonAttrs = {
  profile: {
    // Here you can add some specifications for your nested attributes
    // If you wish, you can have a list of profiles by wrapping this inner object in an array
    firstName: {
      type: 'string',  // `type` will be used in a `typeOf` comparison
      required: true  // `required` will check if the value is present
      // defaultsTo: 'John'  -  is also an option and will bring this value if none is given
      // more options can be added here, you just need to implement some logic on the service
    },
    lastName: {
      type: 'string',
      required: true
    }
  }
}


module.exports = {

  attributes: ModelService.complete({
    profile: {
      // Note that you don't need anything in here
    }
  }),

  beforeValidate: ModelService.validateJSONFactory(jsonAttrs)

};