It seems in sailsjs you can only run and pass one set of query data at a time. For example here is the controller for my homepage:
module.exports = {
index: function (req, res) {
Blog.find()
.limit(3)
.sort('createdAt desc')
.where({ isPublished: 1 })
.exec(function(err, posts) {
if (err) return next(err);
res.view({
layout: "homeLayout",
posts:posts
});
});
}
};
How would I query data from some other model and pass it to my view along with the blog data Im already passing?
You can use Promises to do so. It's actually an excellent usecase. I use Q, which is what Waterline (Sail's ORM) use behind the scene.
You can see below an example of code where I retrieve data from a first model, and then, using the data I retrieved, I query other models to get some more data (in parallel), and in the end, I send the result back to the view.
SomeModel.findOne(criterias).then(function(result) {
Q.all([
SomeOtherModel.getSomething(result),
YetAnotherModel.getSomethingElse(result)
]).spread(function(someOtherResult, yetAnotherResult) {
var data = {
thing: result,
stuff: someOtherResult,
otherthing: yetAnotherResult
};
return res.view(data);
});
}).fail(function(reason) {
return res.view(reason);
});
The getSomething()
function should return a promise, standard finder from Sails will work transparently (just don't pass the callback). As per this other question it appears that standard finder do not behave exactly like Q promises, the answer I gave there should help get a more consistant behavior.
More on Q and how it works in the doc !
You could also use async.auto (see below). Here's a link to the complete sails repo example.
var async = require('async'),
_ = require('lodash');
module.exports = {
index: function (req, res) {
async.auto({
// Get the blog posts
posts: function (cb) {
Blog.find()
.where({ isPublished: 1 })
.limit(5)
.sort('createdAt DESC')
.exec(cb);
},
// Get some more stuff
// (this will happen AT THE SAME TIME as `posts` above)
otherThings: function (cb) {
OtherThing.find()
.limit(30)
.exec(cb);
},
// Get comments
// (we'll wait until `posts` is finished first)
comments: ['posts', function (cb, async_data) {
// Get `posts`
// (the second argument to cb() back in `posts`)
// Used map to make sure posts are an array of ids and not just an object.
var posts = async_data.posts.map(function (item){ return item.id});
// Get comments that whose `post_id` is equal to
// the id of one of the posts we found earlier
Comment.find()
.where({ post_id: posts })
.exec(cb);
}]
},
function allDone (err, async_data) {
// If an error is passed as the first argument to cb
// in any of the functions above, then the async block
// will break, and this function will be called.
if (err) return res.serverError(err);
var posts = async_data.posts;
var comments = async_data.comments;
var otherThings = async_data.otherThings;
// Fold the comments into the appropriate post
// An in-memory join
_.map(posts, function (post) {
var theseComments =
_.where(comments, { post_id: post.id });
post.comments = theseComments;
});
// Show a view using our data
res.json({
// layout: 'homeLayout',
posts: posts,
otherThings: otherThings
});
});
}
};
I have figured out a few ways to accomplish this. The first way is to nest your queries, eg.
Blog.find()
.limit(30)
.sort('createdAt desc')
.where({ isPublished: 1 })
.exec(function(err, posts) {
SomeOtherModel.find()
.limit(5)
.sort('createdAt desc')
.where({ isPublished: 1 })
.exec(function(err, otherdata) {
res.view({
posts: posts,
otherdata: otherdata
});
});
});
The second way is to use promises (I wasnt aware of this previously)
User.findOne()
.where({ id: 2 })
.then(function(user){
var comments = Comment.find({userId: user.id}).then(function(comments){
return comments;
});
return [user.id, user.friendsList, comments];
}).spread(function(userId, friendsList, comments){
// Promises are awesome!
}).fail(function(err){
// An error occured
})
The third way (I ended up going with this) is to create a policy (specific to sailsjs but is express middleware)
// saved as /api/policies/recentPosts.js
// also need to add a rule to /config/policies.js
module.exports = function (req, res, ok) {
Blog.find()
.limit(3)
.sort('createdAt desc')
.where({ isPublished: 1 })
.exec(function(err, footerposts) {
res.footerposts = footerposts;
return ok();
});
};
Doing it this way you dont need to pass anything to your view however Im not sure if its good practice to randomly add data to the response object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With