问题
In sails.js 0.10 I'm trying to do the following
// user.js
module.exports = {
attributes: {
uuid: {
type: 'string',
primaryKey: true,
required: true
} ,
profile: {
firstname: 'string',
lastname: 'string',
birthdate: 'date',
required: true
}
}
};
I'm getting an error when trying to create a user and sailsJS doesn't recognize the "profile" attribute. I'm not sure if sails supports the nested JSON structure, and if it does I'm not sure how to structure it.
error: Sent 500 ("Server Error") response
error: Error: Unknown rule: firstname
I've tried the following but it failed too
// user.js
module.exports = {
attributes: {
uuid: {
type: 'string',
primaryKey: true,
required: true
} ,
profile: {
firstname: {type: 'string'},
lastname: {type: 'string'},
birthdate: 'date',
required: true
}
}
};
I know there is an attribute called "JSON" with sailsJS 0.10, but not sure how that will fit this module.
回答1:
Waterline doesn't support defining nested schemas, but you can use the json
type to store embedded objects in your model. So, you would do:
profile: {
type: 'json',
required: true
}
And then you could create User instances like:
User.create({profile: {firstName: 'John', lastName: 'Doe'}})
the difference is that the firstName
and lastName
fields won't be validated. If you want to validate that the schema of the embedded profile
object matches what you want, you'll have to implement the beforeValidate()
lifecycle callback in your model class:
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();
}
}
回答2:
Following @sgress454 answer I've created some extra logic to help with this problem.
Since I'm using this "nested models" more than once, I created a service with a factory for those beforeValidade()
lifecycle callbacks.
Here how it looks like:
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>
And, in the model, I have this:
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)
};
I know it's not perfect yet, maybe I should make this a hook but I'm still confused on the best way to do that.
来源:https://stackoverflow.com/questions/24020055/sails-js-nested-models