I have a question on sails js:
How can I write sails function on model To use in Controler? like:
beforeValidation / fn(values, cb)
beforeCreate / fn(values, cb)
afterCreate / fn(newlyInsertedRecord, cb)
If you are actually trying to use one of the lifecycle callbacks, the syntax would look something like this:
var uuid = require('uuid');
// api/models/MyUsers.js
module.exports = {
attributes: {
id: {
type: 'string',
primaryKey: true
}
},
beforeCreate: function(values, callback) {
// 'this' keyword points to the 'MyUsers' collection
// you can modify values that are saved to the database here
values.id = uuid.v4();
callback();
}
}
Otherwise, there are two types of methods you can create on a model:
instance methods
collection methods
Methods placed inside the attributes object will be "instance methods" (available on an instance of the model). i.e.:
// api/models/MyUsers.js
module.exports = {
attributes: {
id: {
type: 'string',
primaryKey: true
},
myInstanceMethod: function (callback) {
// 'this' keyword points to the instance of the model
callback();
}
}
}
this would be used as such:
MyUsers.findOneById(someId).exec(function (err, myUser) {
if (err) {
// handle error
return;
}
myUser.myInstanceMethod(function (err, result) {
if (err) {
// handle error
return;
}
// do something with `result`
});
}
Methods placed outside the attributes object but inside the model definition are "collection methods", i.e.:
// api/models/MyUsers.js
module.exports = {
attributes: {
id: {
type: 'string',
primaryKey: true
}
},
myCollectionMethod: function (callback) {
// 'this' keyword points to the 'MyUsers' collection
callback();
}
}
the collection method would be used like this:
MyUsers.myCollectionMethod(function (err, result) {
if (err) {
// handle error
return;
}
// do something with `result`
});
P.S. the comments about what the 'this' keyword will be are assuming that you use the methods in a normal way, i.e. calling them in the way that I described in my examples. If you call them in a different way (i.e. saving a reference to the method and calling the method via the reference), those comments may not be accurate.
Related
var config = require('config.json');
var mongo = require('mongoskin');
var db = mongo.db(config.connectionString, { native_parser: true });
module.exports.getNextSequence = function (name) {
var temp;
db.collection("counters").findAndModify(
{ _id: name }, // query
[], // represents a sort order if multiple matches
{ $inc: { seq: 1 } }, // update statement
{ new: true }, // options - new to return the modified document
function (err, doc) {
temp = doc.value.seq;
console.log(temp); // <-- here the temp is getting printed correctly
}
);
return temp;
}
Using the above code, I am not able to return the value of doc.value.seq. When doing console.log(obj.getNextSequence) it prints undefined.
I want the function to return the value of doc.value.seq.
I'm not familiar with mongoskin so I'm not positive this is correct, but a database query is typically asynchronous, so you need to access the queried value via a callback.
I'm guessing your "getNextSequence" function is returning the "temp" variable before the database query completes (i.e. before the "temp = doc.value.seq" statement).
Try something like this:
module.exports.getNextSequence = function (name, callback) {
var temp;
db.collection("counters").findAndModify(
{ _id: name }, // query
[], // represents a sort order if multiple matches
{ $inc: { seq: 1 } }, // update statement
{ new: true }, // options - new to return the modified document
function (err, doc) {
temp = doc.value.seq;
callback(temp);
}
);
}
Then access "temp" from within the callback passed to getNextSequence.
findAndModify is an asynchronous function. Your console.log line will run after you return temp, which will therefore be undefined. In order to get this to work, you'll want to use an asynchronous approach of your own. There are two available approaches in your situation.
Callbacks:
You're already using a callback, which you provide as the final argument to findAndModify. You could extend this approach and feed this into a callback of your own, as follows:
module.exports.getNextSequence = function (name, callback) {
db.collection("counters").findAndModify(
{ _id: name },
[],
{ $inc: { seq: 1 } },
{ new: true },
function (err, doc) {
if (err) {
return callback(err);
}
callback(null, doc.value.seq);
}
);
}
Of course, this will require you to pass a callback into getNextSequence and follow the callback pattern upstream. You might also want to handle the error from mongoskin and do some handling of your own.
Promises:
If you don't provide a callback to findAndModify, it will return a promise, which you can chain on to, as follows:
module.exports.getNextSequence = function (name) {
return db.collection("counters").findAndModify(
{ _id: name },
[],
{ $inc: { seq: 1 } },
{ new: true }
).then(function (doc) {
return doc.value.seq;
});
}
Again, this will require you to follow the promise pattern upstream. You'll want to read up on promises if you choose this approach, so that you can correctly handle errors, which I have not addressed in the example above.
am using sails-sqlserver as my adapter am just trying the create a new row in the database in one of the follwing models.
This is the first model :
// Roles.js
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
name: {
type: 'string'
},
approval_level: {
model: 'approval_levels'
},
specified: {
type: 'boolean'
},
entity_operations: {
collection: 'entity_operations',
via: 'roles',
dominant: true
},
users: {
collection: 'system_users',
via: 'role'
}
},
createRole: function (name, approval_level, cb) {
values = {
name: name,
approval_level: approval_level
};
Roles.create(values).exec(cb);
},
getAll: function (cb) {
Roles.find().exec(cb);
}
};
This is the second model :
// Entity_Operations.js
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
entity_name: {
type: 'string'
},
operation: {
model: 'operations'
},
roles: {
collection: 'roles',
via: 'entity_operations'
}
},
getAll: function (cb) {
Entity_operations.find().exec(cb);
}
};
These two models have a many to many relationship together what am trying to do is just this :
Entity_operations.create({
entity_name: 'example',
operation: 6
}).exec((err, entity: Entity_operations) => {
console.log(entity);
});
then this error comes out without explaining anything that could help me know from where this error is coming from :
/opt/nodejs/back-heaven/dev/node_modules/sails-sqlserver/lib/adapter.js:435
Object.keys(connections[connection].collections[collection].definition).forEach(function(key) {
^
TypeError: Cannot read property 'collections' of undefined
at Object.getPrimaryKey (/opt/nodejs/back-heaven/dev/node_modules/sails-sqlserver/lib/adapter.js:435:42)
at Object.create (/opt/nodejs/back-heaven/dev/node_modules/sails-sqlserver/lib/adapter.js:374:24)
at module.exports.create (/opt/nodejs/back-heaven/dev/node_modules/waterline/lib/waterline/adapter/dql.js:84:13)
at child.createValues (/opt/nodejs/back-heaven/dev/node_modules/waterline/lib/waterline/query/dql/create.js:220:16)
at /opt/nodejs/back-heaven/dev/node_modules/waterline/lib/waterline/query/dql/create.js:74:20
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:726:13
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:52:16
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:269:32
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:44:16
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:723:17
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:167:37
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:52:16
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:269:32
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:44:16
at child.<anonymous> (/opt/nodejs/back-heaven/dev/node_modules/waterline/lib/waterline/utils/schema.js:152:44)
at fn (/opt/nodejs/back-heaven/dev/node_modules/waterline/lib/waterline/utils/callbacksRunner.js:41:10)
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:181:20
at iterate (/opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:262:13)
at Object.async.forEachOfSeries.async.eachOfSeries (/opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:281:9)
at Object.async.forEachSeries.async.eachSeries (/opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:214:22)
at Object.runner.beforeCreate (/opt/nodejs/back-heaven/dev/node_modules/waterline/lib/waterline/utils/callbacksRunner.js:44:9)
at /opt/nodejs/back-heaven/dev/node_modules/waterline/lib/waterline/query/dql/create.js:180:17
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:718:13
at iterate (/opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:262:13)
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:274:29
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:44:16
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:723:17
at /opt/nodejs/back-heaven/dev/node_modules/async/lib/async.js:167:37
when i tried the same code in a controller it succeded am trying it in a seeder of my own implementation this seeder before it starts i make a sails object programmatically and call sails.load
like this :
let myApp = new Sails();
myApp.load({}, (err) => {
if (err) throw err;
// this execute the seeds in the database seeder class
let seeder = require(`../../api/seeders/${scope.args[0]}`);
seeder.handle(() => {
myApp.lower((err) => {
if (err) throw err;
console.log(`Seeder ` + scope.args[0] + ` finished seeding`);
});
});
});
I also tried with sails.lift() and still the same error.
I have Found out the problem i was just making a callback function that should call sails.lower() which unload or close the sails I was putting it in the wrong place where it was called even before the model creation code starts.just forget to call it in the database callback function.
so , for anyone else who is facing this same problem in sails your problem is that sails actually not loaded or maybe when the operation you were working on started sails was working but, after that for some reason sails stopped which made the models do this weird looking problem I hope sails handle such errors in their code to show out a more expressive error messages.
I hope this helps anyone.
Async instance method
I want to add an instance method to my model Reservation, this instance method will perform a query on the relations model and execute some calculations on it. However, return a value asynchronously ain't possible.
I have a solution which returns a Promise, but that really ain't feasible when having multiple instance methods to return as a json object.
Controller
// controllers/ParkingController
reservationsCount: function(req, res, next){
Parking.find('5751401d54f4ca110020c15b').exec(function(err, parking) {
console.log(parking.reservationsCount())
res.json({});
});
}
Model
// models/Parking
module.exports = {
attributes: {
name: { type: 'string'},
reservations: {
collection: "reservation",
via: "parking",
},
getReservationsCount: function(cb) {
return Parking.findOne(this.id).populate('reservations').exec(function(err, result){
cb(result.reservations.length);
});
},
}
};
Try this (not tested)
Model
getReservationsCount: function() { // no callback since we use Promises
return Parking.findOne(this.id)
.populate('reservations')
.then(function(parking){
return parking.reservations.length;
});
},
Controller
reservationsCount: function(req, res){ // next is useless in sails
Parking.findOne('5751401d54f4ca110020c15b') // findOne to get one object
.then(function(parking) {
return parking.getReservationsCount(); // getReservationsCount returns a Promise
})
.then(function(reservationsCount){
return res.json({reservationsCount});
})
.catch(function(err){
sails.log.error(err);
// handle error
});
}
So here's the deal :
I have an array of objects with a child array of objects
askedAdvices
askedAdvice.replayAdvices
I'm looping trough the parent and foreach looping trough the childs and need to populate() two obejcts (I'm using sails)
The child looks like :
askedAdvices = {
replayAdvices : [{
bookEnd : "<ID>",
user : "<ID>"
}]
}
So my goal is to cycle and populate bookEnd and user with two findOne query, but I'm going mad with the callback hell.
Here's the Models code :
AskedAdvices Model
module.exports = {
schema : false,
attributes: {
bookStart : {
model : 'book'
},
replayAdvices : {
collection: 'replybookend'
},
user : {
model : 'user',
required : true
},
text : {
type : "text"
}
}
};
ReplyBookEnd Model
module.exports = {
schema : false,
attributes: {
bookEnd : {
model : 'book'
},
user : {
model : 'user',
required : true
},
text : {
type : "text"
}
}
};
Here's the Method code :
getAskedAdvices : function(req, res) {
var queryAskedAdvices = AskedAdvices.find()
.populate("replayAdvices")
.populate("user")
.populate("bookStart")
queryAskedAdvices.exec(function callBack(err,askedAdvices){
if (!err) {
askedAdvices.forEach(function(askedAdvice, i){
askedAdvice.replayAdvices.forEach(function(reply, i){
async.parallel([
function(callback) {
var queryBook = Book.findOne(reply.bookEnd);
queryBook.exec(function callBack(err,bookEndFound) {
if (!err) {
reply.bookEnd = bookEndFound;
callback();
}
})
},
function(callback) {
var queryUser = User.findOne(reply.user)
queryUser.exec(function callBack(err,userFound){
if (!err) {
reply.user = userFound;
callback();
}
})
}
], function(err){
if (err) return next(err);
return res.json(200, reply);
})
})
})
} else {
return res.json(401, {err:err})
}
})
}
I can use the async library but need suggestions
Thanks folks!
As pointed out in the comments, Waterline doesn't have deep population yet, but you can use async.auto to get out of callback hell. The trick is to gather up the IDs of all the children you need to find, find them with single queries, and then map them back onto the parents. The code would look something like below.
async.auto({
// Get the askedAdvices
getAskedAdvices: function(cb) {
queryAskedAdvices.exec(cb);
},
// Get the IDs of all child records we need to query.
// Note the dependence on the `getAskedAdvices` task
getChildIds: ['getAskedAdvices', function(cb, results) {
// Set up an object to hold all the child IDs
var childIds = {bookEndIds: [], userIds: []};
// Loop through the retrieved askedAdvice objects
_.each(results.getAskedAdvices, function(askedAdvice) {
// Loop through the associated replayAdvice objects
_.each(askedAdvice.replayAdvices, function(replayAdvice) {
childIds.bookEndIds.push(replayAdvice.bookEnd);
childIds.userIds.push(replayAdvice.user);
});
});
// Get rid of duplicate IDs
childIds.bookEndIds = _.uniq(childIds.bookEndIds);
childIds.userIds = _.uniq(childIds.userIds);
// Return the list of IDs
return cb(null, childIds);
}],
// Get the associated book records. Note that this task
// relies on `getChildIds`, but will run in parallel with
// the `getUsers` task
getBookEnds: ['getChildIds', function(cb, results) {
Book.find({id: results.getChildIds.bookEndIds}).exec(cb);
}],
getUsers: ['getChildIds', function(cb, results) {
User.find({id: results.getChildIds.userIds}).exec(cb);
}]
}, function allTasksDone(err, results) {
if (err) {return res.serverError(err);
// Index the books and users by ID for easier lookups
var books = _.indexBy(results.getBookEnds, 'id');
var users = _.indexBy(results.getUsers, 'id');
// Add the book and user objects back into the `replayAdvices` objects
_.each(results.getAskedAdvices, function(askedAdvice) {
_.each(askedAdvice.replayAdvices, function(replayAdvice) {
replayAdvice.bookEnd = books[replayAdvice.bookEnd];
replayAdvice.user = users[replayAdvice.bookEnd];
});
});
});
Note that this is assuming Sails' built-in Lodash and Async instances; if you're using newer versions of those packages the usage of async.auto has changed slightly (the task function arguments are switched so that results comes before cb), and _.indexBy has been renamed to _.keyBy.
Is it possible to have an asynchronous validator using Sequelize.js? I want to check for the existence of an association before saving a model. Something like this:
User = db.define("user", {
name: Sequelize.STRING
},
{
validate:
hasDevice: ->
#getDevices().success (devices) ->
throw Exception if (devices.length < 1)
return
})
# .... Device is just another model
User.hasMany(Device)
Or is there a way to force that check to run synchronously? (not ideal)
you can use asynchronous validations in v2.0.0.
It works like this:
var Model = sequelize.define('Model', {
attr: Sequelize.STRING
}, {
validate: {
hasAssociation: function(next) {
functionThatChecksTheAssociation(function(ok) {
if (ok) {
next()
} else {
next('Ooops. Something is wrong!')
}
})
}
}
})