Loopback: Where does one put observers? - node.js

I'm relatively new to the loopback game. How can I get observers to work?
For example, I want something to observe whenever user information is changed or a user is created.
Thanks
//this observer will be activated whenever the user is edited or created
User.observe('after save', function(ctx, next) {
var theUserObject = ctx.instance;
if(ctx.isNewInstance){
anotherModel.create(theUserObject.name,theUserObject.ID);
}else{
anotherModel.update(theUserObject.name,theUserObject.ID);
}
next();
});
Is this the correct user of ctx? Where should this code sit? Within the User.js?

Just to put this in answer (see comments above):
In general what you are doing is mostly correct. You want to put operation hooks in the common/models/my-model.js file(s), but that context object (ctx) will change depending on the hook (read the linked documentation above).
In your case, to create a new model, you need to access the app off of the current model and then execute create(), but be sure to put your next() callback in the callback for the create call:
//this observer will be activated whenever the user is edited or created
User.observe('after save', function(ctx, next) {
var theUserObject = ctx.instance;
if(ctx.isNewInstance){
User.app.models.anotherModel.create({name: theUserObject.name, id: theUserObject.ID}, function(err, newInstance) {
next(err);
});
} else {
User.app.models.anotherModel.find({ /* some criteria */ }, function(err, instance) {
if (err) { return next(err); }
if (instance) {
instance.updateAttributes({name: theUserObject.name, id: theUserObject.ID}, function(err) {
next(err);
});
}
});
}
});

Related

What is next() doing in this Express route and why is its order in the code important?

I'm getting confused with next(); I read through this post which essentially says it passes control to the next route. In my code below, having next(); where it is causes me to get "Cannot set headers after they are sent to the client". However, if I comment that out and then restore the else clause of my if statement, it functions correctly both when an incorrect empID is passed as well as when a correct one is. I'm hoping someone could explain what exactly is happening? Why does the position of the next() matter? It seems like it would be called either way?
I'm trying to do what is happening in this post which is add a value to, say req.user, but I haven't been able to get that to work at all so I'm trying the method I have here.
let checkEmp = (req, res, next) => {
db.get("select * from Employee where id = $id", {$id: req.empID},
(err, row) => {
if (err || row === undefined) {
res.status(404).send();
// } else {
// next();
}
});
next();
};
// get all timesheets
timesheetRouter.get("/", getParams, checkEmp, (req, res, next) => {
if (req.empID) {
db.all("select * from Timesheet where employee_id = $id", {$id: req.empID},
(err, rows) => {
if (err) {
next(err);
} else {
return res.status(200).send({timesheets: rows});
}
});
} else {
return res.status(404).send("Nothing to see here");
}
});
Looks like db.get() is probably asynchronous, so in the example as shown, next() will be called before db.get() finishes and it moves on to the next handler. Then, when the db.get() finishes, it tries to send a response, but the response has already been sent by the anonymous function in the main handler. By moving the next() inside of db.get(), you're essentially waiting for it to finish before moving on.

Express: Referencing the parent within the route definition for a child

I am currently studying https://thinkster.io/mean-stack-tutorial/, but am puzzled about the route definition for a child element. For example, in the following...
// create a new comment
router.post('/posts/:post/comments', function(req, res, next) {
var comment = new Comment(req.body);
comment.post = req.post;
comment.save(function(err, comment){
if(err){ return next(err); }
req.post.comments.push(comment);
req.post.save(function(err, post) {
if(err){ return next(err); }
res.json(comment);
});
});
});
... we are trying to define the route for adding a new comment for a particular post. As you can see, we are required to link the parent to the comment...
comment.post = req.post;
... and we are also required to, understandably, save the parent within the comment save function. However, when comes to updating the comment (i.e. increasing the upvotes for a comment) we are only required to do...
// upvote a comment
router.put('/posts/:post/comments/:comment/upvote', function(req, res, next) {
req.comment.upvote(function(err, comment){
if (err) { return next(err); }
res.json(comment);
});
});
... where 'upvote' is a custom schema method...
CommentSchema.methods.upvote = function(cb) {
this.upvotes += 1;
this.save(cb);
};
... why is that we are not required to reference the parent object 'post' here at all? I mean how does the system know exactly which comment to load? I understand that ':post' refers to the parent post, however, we are not even referencing that pre-loaded parent within the method... so how is it being used? Are the correct set of comments automatically loaded from the pre-loaded post parent object, without our need to do this explicitly?
Any guidance would be appreciated.
according with the tutorial :
router.param('post', function(req, res, next, id) {
var query = Post.findById(id);
query.exec(function (err, post){
if (err) { return next(err); }
if (!post) { return next(new Error('can\'t find post')); }
req.post = post;
return next();
});
});
This code will take params from route: post/:post , so basically if you test the route : post/1 , it will set into request object(req) the post with id:1

Middleware defined before model definition, cascade delete failing, appears middleware gets bypassed

This is what I have in my file. What I want to do is that when an Author document is removed, all his Book documents should also be removed. I initially experimented with serial middleware with error handlers wired in but no errors were being logged, Author was being removed but his Books were not.
Then I tried parallel middleware under the assumption that remove() won't be triggered until all its pre-middleware has completed but that doesn't seem to be the case. Author is still being removed but Books are not, and no errors are being logged:
//...
var Book = require('./book-model'');
AuthorSchema.pre('remove', true, function(next, done) {
Book.remove({author: this._id}, function(err) {
if (err) {
console.log(err);
done(err);
}
done();
});
next();
});
AuthorSchema.statics.deleteAuthor = function(authorId, callback) {
var Author = mongoose.model('Author');
Author.remove({_id: authorId}, callback);
};
// ...
module.exports = mongoose.model('Author', AuthorSchema);
So I'm thinking that the middleware is being bypassed otherwise, considering the number of variations that I've tried, I'd have seen at least a couple of errors to indicate that the middleware is indeed being triggered. Unluckily, I just can't seem to put a finger on what I'm doing wrong.
Please advise.
'remove' middleware only runs when the remove instance method is called (Model#remove), not the class method (Model.remove). This is akin to how 'save' middleware is called on save but not on update.
So you'd need to rewrite your deleteAuthor method to something like:
AuthorSchema.statics.deleteAuthor = function(authorId, callback) {
this.findById(authorId, function(err, author) {
if (author) {
author.remove(callback);
} else {
callback(err);
}
});
};

Sailsjs find() callback action

i have a problem with my callbacks, i dont know how i get it back.
index: function(req, res,next) {
User.find().exec(function(err,users) {
if(err) {
//do something
}
res.locals.users = users
return true;
});
Country.find().exec(function(err,countrys) {
//save country
}
console.log(res.locals.users)
res.view({
users: res.locals.users,
country: countrys
user:res.locals.user
});
}
How can i get access to both objects?
Thank you
Your DB calls are running async, that means the view is rendered before the data can be passed to them. So you have to make them in synchronous manner. This can be achived via callback or you can chain your functions instead of callbacks, like this,
index: function(req, res,next) {
User.find().exec(function(err,users) {
if(err) {
//do something
}
else{
res.locals.users = users
Country.find().exec(function(err,countrys) {
if(err) {
//do something
}
else{
console.log(res.locals.users)
res.view({
users: res.locals.users,
country: countrys
user:res.locals.user
});
}
}
}
});
}
Other way is to use callbacks.
If your trying to make it pretty and organised when you keep adding more queries you can use the async library that comes with sails
see this answer as an example
Chaining waterline calls with Promises

rendering results of multiple DB/mongoose queries to a view in express.js

given the async nature of mongoose (or sequelize, or redis) queries, what do you do when you have multiple queries you need to make before rendering the view?
For instance, you have a user_id in a session, and want to retrieve some info about that particular user via findOne. But you also want to display a list of recently logged in users.
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
})
}
// here, current_user isn't populated until the callback fires
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
});
};
So res.render is in the first query callback, fine. But what about waiting on the response from findOne to see if we know this user? It is only called conditionally, so I can't put render inside the inner callback, unless I duplicate it for either condition. Not pretty.
I can think of some workarounds -
make it really async and use AJAX on the client side to get the current user's profile. But this seems like more work than it's worth.
use Q and promises to wait on the resolution of the findOne query before rendering. But in a way, this would be like forcing blocking to make the response wait on my operation. Doesn't seem right.
use a middleware function to get the current user info. This seems cleaner, makes the query reusable. However I'm not sure how to go about it or if it would still manifest the same problem.
Of course, in a more extreme case, if you have a dozen queries to make, things might get ugly. So, what is the usual pattern given this type of requirement?
Yep, this is a particularly annoying case in async code. What you can do is to put the code you'd have to duplicate into a local function to keep it DRY:
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
function render() {
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
}
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
render();
})
} else {
render();
}
});
};
However, looking at what you're doing here, you'll probably need to look up the current player information in multiple request handlers, so in that case you're better off using middleware.
Something like:
exports.loadUser = function (req, res, next) {
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
req.player = player
}
next();
})
} else {
next();
}
}
Then you'd configure your routes to call loadUser wherever you need req.player populated and the route handler can just pull the player details right from there.
router.get("/",function(req,res){
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user Data
function(callback) {
mongoOp.User.find({},function(err,user){
if (err) return callback(err);
locals.user = user;
callback();
});
},
//Load posts Data
function(callback) {
mongoOp.Post.find({},function(err,posts){
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('index.ejs', {userdata: locals.user,postdata: locals.posts})
});
Nowadays you can use app.param in ExpressJS to easily establish middleware that loads needed data based on the name of parameters in the request URL.
http://expressjs.com/4x/api.html#app.param

Resources