Sails.js/Waterline populate deep nested association

Sávio Lucena picture Sávio Lucena · Oct 23, 2014 · Viewed 11k times · Source

I understand that there is no built-in way in Sails.js/Waterline of populating deep nested associations yet, so I am trying to use bluebird promises to accomplish that but I'm running into a problem.

I'm successfully retrieving the user, and all the posts (populated with the images collection) associated with it (console.log shows me that everything is filled properly). However, when I override the property "post" of the user and try to assign the fully populated posts retrieved before, it does not fill properly the images property of Post.js. It is like the ORM is preventing the image collection of Post.js to be manually assigned.

What am I doing wrong? What is the best way of populating deep nested one-to-many associations?

Bellow I've pasted all the code that I'm executing....

// Populate nested association
nested: function (req, res, next){
var username = req.param("id");

User
.findOneByUsername(username)
.populateAll()      
.then(function (user){
    var posts = Post.find({
        "user": user.id
    })
    .populate('images')
    .populate('category')
    .then(function (posts){
        return posts;
    });
    return [user, posts];
})
.spread(function (user, posts){
    user.posts = posts; // This won't work.... It assigns all the fields properly but the images collection attribute
    res.json(user);
}).catch(function (err){
    if (err) return res.serverError(err);
});
}

// --- User.js Model --- //
module.exports = {
   attributes: {
    .....,
    posts: {
        collection: "post",
        via: "user"
    },
    .....
   }
 }

// --- Post.js Model --- //
module.exports = {
    attributes: {
       ....,
       user: {
         model: "user"
       },
       images: {
         collection: "postImage",
         via: "post"
       },
       ....
    }
}

// --- PostImage.js Model --- //
module.exports = {

   attributes: {
     ....,
     post: {
       model: "post"
     }
   },
}

Regards,

Sávio Lucena

Answer

Lu Roman picture Lu Roman · Aug 26, 2015

This might be an old question, but its better to have an answer, so sails.js users can benefit of it.

Your issue here is that when sails returns a record (Inside an array), the keys of that record that correspond to associations, are in fact getters/setters, and it seems that the setter does not allows what you want. You can use Object.getOwnPropertyDescriptor(user, 'posts') to confirm. So what you need to do in order to be able to override that property as you want, is to call .toObject on it, (or clone its properties via _.clone or manually looping but you'll get a lot of junk with it, so stick to the .toObject), in any case you get a new object with the properties you need, and there is no restriction in how you modify it now.

So your code will look like this:

User
.findOneByUsername(username)
.populateAll()      
.then(function (user){
    var posts = Post.find({
        "user": user.id
    })
    .populate('images')
    .populate('category')
    .then(function (posts){
        return posts;
    });
    return [user, posts];
})
.spread(function (user, posts){
    user = user.toObject() // <- HERE IS THE CHANGE!
    user.posts = posts; // It will work now
    res.json(user);
}).catch(function (err){
    if (err) return res.serverError(err);
});
}