Sails.js verschachtelte Modelle
Frage
In Sails.js 0.10 versuche ich Folgendes zu tun
// user.js
module.exports = {
attributes: {
uuid: {
type: 'string',
primaryKey: true,
required: true
} ,
profile: {
firstname: 'string',
lastname: 'string',
birthdate: 'date',
required: true
}
}
};
Ich erhalte eine Fehlermeldung, wenn ich versuche, einen Benutzer zu erstellen, und SailsJS erkennt das Attribut „Profil“ nicht.Ich bin nicht sicher, ob Sails die verschachtelte JSON-Struktur unterstützt, und wenn ja, bin ich nicht sicher, wie ich sie strukturieren soll.
error: Sent 500 ("Server Error") response
error: Error: Unknown rule: firstname
Ich habe Folgendes versucht, aber es ist auch fehlgeschlagen
// user.js
module.exports = {
attributes: {
uuid: {
type: 'string',
primaryKey: true,
required: true
} ,
profile: {
firstname: {type: 'string'},
lastname: {type: 'string'},
birthdate: 'date',
required: true
}
}
};
Ich weiß, dass es in SailsJS 0.10 ein Attribut namens „JSON“ gibt, bin mir aber nicht sicher, wie das zu diesem Modul passt.
Lösung
Waterline unterstützt die Definition verschachtelter Schemata nicht, Sie können jedoch das verwenden json
Geben Sie ein, um eingebettete Objekte in Ihrem Modell zu speichern.Sie würden also Folgendes tun:
profile: {
type: 'json',
required: true
}
Und dann könnten Sie Benutzerinstanzen erstellen wie:
User.create({profile: {firstName: 'John', lastName: 'Doe'}})
Der Unterschied besteht darin, dass die firstName
Und lastName
Felder werden nicht validiert.Wenn Sie das Schema des eingebetteten validieren möchten profile
Wenn das Objekt Ihren Wünschen entspricht, müssen Sie es implementieren beforeValidate()
Lebenszyklusrückruf in Ihrer Modellklasse:
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();
}
}
Andere Tipps
Nachfolgend Antwort von @sgress454 Ich habe eine zusätzliche Logik erstellt, um dieses Problem zu lösen.
Da ich diese „verschachtelten Modelle“ mehr als einmal verwende, habe ich für diese einen Dienst mit einer Factory erstellt beforeValidade()
Lebenszyklusrückrufe.
So sieht es aus:
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>
Und im Modell habe ich Folgendes:
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)
};
Ich weiß, dass es noch nicht perfekt ist, vielleicht sollte ich daraus einen Hook machen, aber ich bin immer noch verwirrt, wie ich das am besten machen kann.