I am trying to set a value to the values object, but sails ignores it.
attributes: {
title: {
type: 'string',
required: true
},
testAttr: {
type: 'integer'
}
}
beforeCreate: function (values, cb) {
values.testAttr = 1;
cb();
},
My values object does NOT contain a testAttr value when it enters the beforeCreate method as this value is not sent via the request.
It could seem like it wont add/update this unless it is present on the values object to begin with.
What is the correct way to do this?
I think you have it almost right. The callback cb expects two arguments: an error, and a values hash. You just need to pass on your values:
beforeCreate: function (values, cb) {
values.testAttr = 1;
cb(null, values);
},
Related
My JSON response contains a field first_name but I want my Mongoose model to represent this field as firstName. Is this possible and if so then how?
You can create a new object with different property names from the one Mongoose returns. A nice way of doing this is to create a transform function. For example, let's say this is your schema:
{
firstName: { type: String, required: true },
lastName: { type: String, required: true }
}
Then you can use this function to create a new object with the desired property names:
function transformDocument (doc) {
return {
first_name: doc.firstName,
last_name: doc.lastName
}
}
Then, when you query the DB, you apply this function to the response:
Person.findOne({ firstName: 'John', lastName: 'Smith' })
.then(transformDocument)
Doug W has a good solution, but if you don't want to be using Promises and chaining .thens, then you can simply add options to the schema like this:
const mongoose = require ('mongoose'); // I am using v5.9.1 at the moment
const { Schema } = mongoose.Schema;
// Specify an options object
const options = {
toJSON: {
versionKey: false
}
// If you ever send the query result as an object,
// you may remove it from there, too, if you wish
// toObject: {
// versionKey: false
// }
};
// Attach the options object to the schema by
// passing it into Schema as the second argument
const mySchema = new Schema({
/** define your schema */
}, options);
This will still save __v to the document in the database. But it will not appear on the json/object when it is the result of a mongoose query.
Besides setting versionKey: false in the options, you may also specify a transform function:
/* ... */
// Specify an options object
const options = {
toJSON: {
// versionKey: false,
transform: function(doc, ret) {
// ret is the object that will be returned as the result
// (and then stringified before being sent)
delete ret.__v;
return ret;
}
}
};
/* ... */
I know this question is nearly two years old, but I needed an answer to this question, and google was not kind to me at the time. I figured it out, and now I'm hoping someone else will be looking for an answer here and find that they have options. Pun not originally intended.
I am trying to create CRUD app in sails js, and i am able to post data to my DB what i noticed is when i insert data on success sails return whole object. But if we don't want certain fields in response then how can we restrict it. Please help thanks.
module.exports = {
attributes : {
username : {
type: 'string',
required: true
},
password : {
type: 'string',
required: true
},
email : {
type: 'string',
required: true,
unique: true
}
},
toJson: function() {
var obj = this.toObject();
delete obj.password;
return obj;
},
beforeCreate: function(attribute, callback) {
console.log(attribute.password);
require('bcrypt').hash(attribute.password, 10, function(err, encryptedPassword) {
sails.log(err);
attribute.password = encryptedPassword;
sails.log(encryptedPassword);
callback();
});
}
};
#arbuthnott is partly correct above -- you do need toJSON rather than toJson -- but more importantly, the function needs to go inside the attributes dictionary, since it is an instance method:
attributes : {
username : {
type: 'string',
required: true
},
password : {
type: 'string',
required: true
},
email : {
type: 'string',
required: true,
unique: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
}
I think the responses through sails default REST api for models runs them through .toJSON before returning, so you are doing this the right way.
However, you may have a case issue, like you should define .toJSON with uppercase instead of .toJson. Try making that switch and see if it solves your problem.
UPDATE
Sounds like this is not solving your issue. The sails docs from here say:
The real power of toJSON relies on the fact every model instance sent out via res.json is first passed through toJSON. Instead of writing custom code for every controller action that uses a particular model (including the "out of the box" blueprints), you can manipulate outgoing records by simply overriding the default toJSON function in your model. You would use this to keep private data like email addresses and passwords from being sent back to every client.
That sounds pretty explicitly like what we are trying to do, so maybe this is a sails bug. Perhaps it applies to find, but not create. Is that password returned when simply finding an existing user?
If you must, a sure way around this would be to override the default create action in your UserController:
create: function(req, res) {
User.create(req.body).exec(function(err, user) {
if (err) {
return res.json(err);
}
// explicitly call your own toJSON() to be sure
return res.send(user.toJSON());
});
},
This isn't ideal, especially if you have many model properties you want to hide in many api calls. But it will get the job done.
password: { type: 'string', required: true, protected: true }
protected:true is now deprecated on sails v1.0
You can use instead of that customToJSON
customToJSON: function() {
// Return a shallow copy of this record with the password and ssn removed.
return _.omit(this, ['password', 'ssn'])
}
password: { type: 'string', required: true, protected: true }
You can do this also.
Assuming I had the following model, what is the best practice in Sails.js to calculate the sum attribute from one or more non-persistent attributes on the create API call?
In this example I want to extend the default create behaviour of this models API's to accept the additional attributes valueOne and valueTwo, and then calculate the sum attribute based on a simple valueOne + valueTwo calculation.
Ideally without losing the out of the box validation on other fields on the model, e.g name, but not requiring sum to be submitted at the API level, while still being required on the model itself.
api/models/CalculatedData.js
module.exports = {
attributes: {
name: {
type: 'string',
required: true
},
sum: {
type: 'integer',
required: true
}
}
};
There are many ways you could do this.
If there is only one controller where you populate this model, then you could calculate the sum in your controller and pass the result to the model create method.
Or you could have a service that does that for you. Though a better way would be to have a custom model method that populates the model.
module.exports = {
attributes: {
// your attributes
},
customCreate: function (options, cb) {
options.sum = options.values.reduce(function (a, b) {
return a + b;
});
delete options.values;
Model.create(options).exec(cb);
}
};
Edit
A better way is to use a beforeCreate life-cycle callback
module.exports = {
attributes: {
// your attributes
},
beforeCreate: function (options, cb) {
options.sum = options.values.reduce(function (a, b) {
return a + b;
});
delete options.values;
return cb();
}
};
I am using mongoose's toJSON support as below:
userSchema.set('toJSON', { getters: true, virtuals: true, minimize: false })
now in the returned result from a mongoose object's toJSON() method call, I want to remove certain sensitive fields. How to do that?
In different way to explain the issue:
Some certain fields like 'password', 'token' which we would need only in query, but not in returned result, how to hide them from returning from all kind of queries?
Update: This finally what I ended up with and working like a charm:
userSchema.options.toJSON = {
getters: true,
virtuals: true,
minimize: false,
transform: function (doc, ret, options) {
delete ret.password
delete ret.authToken
return ret
}
}
you can customize how toJSON works on your schema like this:
/**
* adjust toJSON transformation
*/
mySchema.options.toJSON = {
transform: function(doc, ret, options) {
ret.id = ret._id;
delete ret.password;
delete ret.token;
delete ret._id;
delete ret.__v;
return ret;
}
};
doc is the document to be serialized, ret is the plain JS object that will be transformed into JSON. You may then manipulate ret however you want.
This is how I remove the password field.
userSchema.methods.toJSON = function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
Let's say I have an Invoice model in SailsJS. It has 2 date attributes: issuedAt and dueAt. How can I create a custom validation rule that check that the due date is equal or greater than the issued date?
I tried creating a custom rule, but it seems I cannot access other properties inside a rule.
module.exports = {
schema: true,
types: {
duedate: function(dueAt) {
return dueAt >= this.issuedAt // Doesn't work, "this" refers to the function, not the model instance
}
},
attributes: {
issuedAt: {
type: 'date'
},
dueAt: {
type: 'date',
duedate: true
}
}
};
I hope you found a solution now, but for those interested to a good way to handle this i will explain my way to do it.
Unfortunatly as you said you can't access others record attributes in attribute customs validation function.
#Paweł Wszoła give you the right direction and here is a complete solution working for Sails#1.0.2 :
// Get buildUsageError to construct waterline usage error
const buildUsageError = require('waterline/lib/waterline/utils/query/private/build-usage-error');
module.exports = {
schema: true,
attributes: {
issuedAt: {
type: 'ref',
columnType: 'timestamp'
},
dueAt: {
type: 'ref',
columnType: 'timestamp'
}
},
beforeCreate: (record, next) => {
// This function is called before record creation so if callback method "next" is called with an attribute the creation will be canceled and the error will be returned
if(record.dueAt >= record.issuedAt){
return next(buildUsageError('E_INVALID_NEW_RECORD', 'issuedAt date must be equal or greater than dueAt date', 'invoice'))
}
next();
}
};
beforeCreate method in model as first param takes values. The best place for this kind of validation I see here.
beforeCreate: (values, next){
if (values.dueAt >= values.issuedAt) {
return next({error: ['...']})
}
next()
}