modèles imbriqués sails.js
Question
Dans sails.js 0.10, j'essaie de faire ce qui suit
// user.js
module.exports = {
attributes: {
uuid: {
type: 'string',
primaryKey: true,
required: true
} ,
profile: {
firstname: 'string',
lastname: 'string',
birthdate: 'date',
required: true
}
}
};
Je reçois une erreur lorsque j'essaie de créer un utilisateur et sailsJS ne reconnaît pas l'attribut "profil".Je ne sais pas si Sails prend en charge la structure JSON imbriquée, et si c'est le cas, je ne sais pas comment la structurer.
error: Sent 500 ("Server Error") response
error: Error: Unknown rule: firstname
J'ai essayé ce qui suit mais cela a également échoué
// user.js
module.exports = {
attributes: {
uuid: {
type: 'string',
primaryKey: true,
required: true
} ,
profile: {
firstname: {type: 'string'},
lastname: {type: 'string'},
birthdate: 'date',
required: true
}
}
};
Je sais qu'il existe un attribut appelé "JSON" avec sailsJS 0.10, mais je ne sais pas comment cela s'adaptera à ce module.
La solution
Waterline ne prend pas en charge la définition de schémas imbriqués, mais vous pouvez utiliser l'option json
tapez pour stocker les objets incorporés dans votre modèle.Donc tu ferais :
profile: {
type: 'json',
required: true
}
Et puis vous pouvez créer des instances utilisateur telles que :
User.create({profile: {firstName: 'John', lastName: 'Doe'}})
la différence est que le firstName
et lastName
les champs ne seront pas validés.Si vous souhaitez valider que le schéma du système intégré profile
l'objet correspond à ce que vous voulez, vous devrez implémenter le beforeValidate()
rappel du cycle de vie dans votre classe de modèle :
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();
}
}
Autres conseils
Suivant @ réponse sgress454 J'ai créé une logique supplémentaire pour résoudre ce problème.
Comme j'utilise ces "modèles imbriqués" plus d'une fois, j'ai créé un service avec une usine pour ceux-ci. beforeValidade()
rappels du cycle de vie.
Voici à quoi ça ressemble :
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>
Et dans le modèle j'ai ça :
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)
};
Je sais que ce n'est pas encore parfait, je devrais peut-être en faire un crochet, mais je ne sais toujours pas quelle est la meilleure façon de le faire.