вложенные модели парусов.js

В 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 не распознает атрибут «профиль». Я не уверен, поддерживает ли паруса вложенную структуру 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", но не уверен, как он подойдет этому модулю.


person Kaiusee    schedule 03.06.2014    source источник


Ответы (2)


Waterline не поддерживает определение вложенных схем, но вы можете использовать тип json для хранения встроенных объектов в вашей модели. Итак, вы бы сделали:

profile: {
    type: 'json',
    required: true
}

И тогда вы можете создавать экземпляры User, например:

User.create({profile: {firstName: 'John', lastName: 'Doe'}})

разница в том, что поля firstName и lastName не будут проверяться. Если вы хотите проверить, соответствует ли схема встроенного объекта profile тому, что вы хотите, вам придется реализовать обратный вызов жизненного цикла beforeValidate() в классе вашей модели:

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();
    }
}
person sgress454    schedule 03.06.2014
comment
Спасибо! Это действительно полезно - person Kaiusee; 03.06.2014

Следуя @sgress454 answer, я создал дополнительную логику, чтобы решить эту проблему.

Поскольку я использую эти «вложенные модели» более одного раза, я создал сервис с фабрикой для этих beforeValidade() обратных вызовов жизненного цикла.


Вот как это выглядит:

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)

};


Я знаю, что это еще не идеально, может быть, мне следует сделать это крючком, но я все еще не знаю, как лучше всего это сделать.

person pekapa    schedule 08.03.2017