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
Related
As a personal project, I'm trying to build a social media site for teddy bear collectors. I would like users to be able to make a "collection" page which they can populate with individual profile pages for each of their bears. Finally, I would like other users to be able to comment on both the collection page and the individual profile pages.
However, I'm running into an error on the "/new" route for comments on the individual profile pages. I can't get it to find the id for the parent collection.
Below is the code I'm working with. I start by finding the id for the collection, then I get try to get the id for the bear (individual profile page). However, the process keeps getting caught at the first step.
var express = require("express");
var router = express.Router({mergeParams: true});
var Bear = require("../models/bear");
var Comment = require("../models/comment");
var Collection = require("../models/collection");
var middleware = require("../middleware");
//comments new
router.get("/new", middleware.isLoggedIn, function(req, res) {
Collection.findById(req.params.id, function(err, foundCollection) {
if(err || !foundCollection){
req.flash("error", "Collection not found");
return res.redirect("back");
}
Bear.findById(req.params.bear_id, function(err, foundBear) {
if(err){
res.redirect("back");
} else {
res.render("bcomments/new", {collection_id: req.params.id, bear: foundBear, collection: foundCollection});
}
});
});
});
//bcomments create
router.post("/", middleware.isLoggedIn, function(req, res){
Collection.findById(req.params.id, function(err, foundCollection) {
if(err || !foundCollection){
req.flash("error", "Collection not found");
return res.redirect("back");
}
//look up bear using id
Bear.findById(req.params.id, function(err, foundBear){
if(err){
console.log(err);
res.redirect("/bears" + bear._id);
} else {
//create new comment
Comment.create(req.body.comment, function(err, comment){
if(err){
req.flash("error", "Something went wrong");
console.log(err);
} else {
//add username and id to comment
comment.author.id = req.user._id;
comment.author.username = req.user.username;
//save comment
comment.save();
//connect new comment to bear
bear.comments.push(comment);
bear.save();
//redirect bear show page
req.flash("success", "Successfully added comment");
res.redirect("/collections/" + foundCollection._id + "/bears/" + foundBear._id);
}
});
}
});
});
});
So, instead of rendering the new comment form, it hits a "null" error and redirects back at the first if statement.
If anyone can help me figure this out, I'd be exceedingly grateful.
Thank you.
I think the problem is that you are defining the path "/new" (which has no parameters) and trying to access req.params.id. If you expect to have the parameter id you should define it in the path like this: router.get("/new/:id", .... Check the Express oficial documentation for more details.
EDIT:
You may have mixed req.params with req.query and req.body. If you are passing parameters in the request, you must access them through req.query (for example: req.query.id or req.query.bear_id) in the case of GET and DELETE or through req.body in POST and PUT.
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);
});
}
});
}
});
I try to remove object by id , but get error "[TypeError: Cannot read property '$set' of undefined]" what can be wrong?
var remove = function(req, res, next) {
var id = req.urlParams.id ;
req.urlParams.model.findByIdAndRemove(id,function(err, doc){
console.log(err);
if (err) { return sendError(res,err) }
var data = JSON.stringify(req.body);
...
}
id is initialized and object with that id exist
It seems a little strange that you're looking for your mongoose model in the urlParams. I would have expected something more like
function remove(req, res, model, next){
model.findByIdAndRemove(req.params.id, function(err)....
}
I don't know your whole code but, if your goal is remove object that _id has req.urlParams.id, following code would help you.
var remove = function(req, res, next) {
yourModel.remove({_id: req.urlParams.id},function(err, doc){
console.log(err);
if (err) { return sendError(res,err) }
else{ //do something}
}
I have a question : console.log(req.urlParams.id) work?
if you could type your code detail, I can help you more
var id = mongoose.Types.ObjectId((req.urlParams.id).trim());
loopback.getCurrentContext() is null for me. Why? I need to grab the current User from the context. The operation hook's ctx is not the same context and does not contain what I need as far as I can tell.
Customer.observe('before save', function checkPermission(ctx, next) {
//do I have a loopback context that works?
var context = loopback.getCurrentContext();
console.log("context is: " + context);//null!
});
Thanks
I know this question is old but this might help some people
according to loopback doc
https://loopback.io/doc/en/lb3/Using-current-context.html
In LoopBack 2.x, this feature is disabled by default for compatibility reasons. To enable, add
first you need to add this to your customer model
"injectOptionsFromRemoteContext": true
then:
Customer.observe('before save', function checkPermission(ctx, next) {
//ctx.options returns tokenid, userid & ttl
console.log(ctx.options);
return next();
});
This isn't PostgreSQL specific, but it is a bug.
https://github.com/strongloop/loopback/issues/878#issuecomment-128417677
I think you should add a pre-processing middleware to populate the context with the current user.
/server/server.js
app.use(loopback.context());
app.use(loopback.token());
app.use(function setCurrentUser(req, res, next) {
if (!req.accessToken) {
return next();
}
app.models.Customer.findById(req.accessToken.userId, function(err, user) {
if (err) {
return next(err);
}
if (!user) {
return next(new Error('No user with this access token was found.'));
}
var loopbackContext = loopback.getCurrentContext();
if (loopbackContext) {
loopbackContext.set('currentUser', user);
}
next();
});
});
/common/models/customer.js
var loopback = require('loopback');
module.exports = function(Customer) {
Customer.observe('before save', function checkPermission(ctx, next) {
var context = loopback.getCurrentContext();
var currentUser = context && context.get('currentUser');
console.log(currentUser);
next();
});
};
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