سؤال

Im doing a forum api, where a forum has many threads, thread has many posts and post could have many post.

The relations are done like this:

var PostSchema = new Schema({
  text: String,
  authorId: String,
  slug: Number,
  posts: [{ type: Schema.Types.ObjectId, ref: 'Post'}],
  created: { type: Date, default: Date.now }
});

The parent model has a list of ids of son model.

I made my controller like this:

var util = require('util'),
  mongoose = require('mongoose'),
  Forum = mongoose.model('Forum'),
  Thread = mongoose.model('Thread'),
  Post = mongoose.model('Post'),
  async = require('async');

exports.show = function(req, res){
  var forums;

  var getThreads = function(forum) {
    return forum.populate('threads', function(err, _forum){
      if(err) throw new Error(err);
      forum.threads = _forum.threads;
      forum.threads.forEach(getPosts);
      return callback(err);
    }); 
  };

  var getPosts = function(thread) {
    return thread.populate('posts', function(err, _thread){
      if(err) throw new Error(err);
      thread.posts = _thread.posts;
      thread.posts.forEach(getComments);
      return callback(err);
    });
  };

  var getComments = function(post) {
    return post.populate('posts', function(err, _post){
      if(err) throw new Error(err);
      post.posts = _post.posts;
      post.posts.forEach(getComments);
      return callback(err);
    });
  };

  async.parallel([
    function(callback) {
      return Forum.find({ ownerId: req.params.owner_id }).exec(function(err, _forums) {
        if(err) throw new Error(err);
        forums = _forums;
        forums.forEach(getThreads);
        return callback(err);
      });
    }
  ], function(err){
      res.json(forums);
    }
  );

};

I need to made the complete forum object, then use this on the response, as posts has posts i cannot just do a nested populate.

I tried to use the async lib, but it execute the callback function before the promises.

How can i build the complete forum object?

هل كانت مفيدة؟

المحلول

You need to properly handle your tree structure in an asynchronous way. Try this approach:

(I haven't tested, but hope it works)

// ...
var Forum = mongoose.model('Forum');

exports.show = function(req, res){
  //Get the owner's forums
  Forum.find({ ownerId: req.params.owner_id }).exec(function(err, forums) {
    if(err) throw new Error(err);
    if(!forums.length) return response.json(forums); //Send an empty array if no forums where found

    //Build forums one by one
    var forum = forums.shift();
    buildForum(forum, function () {
      forum = forums.shift();
      if (forum) {
        buildForum(forum, this);
      } else {
        //All forums were built.
        res.json(forums);
      };
    });
  });

  var buildForum = function (forum, onSuccess) {
    forum.populate('threads', function(err, forum){
      if(err) throw new Error(err);
      if(!forum.threads.length) return onSuccess();

      //Build threads one by one
      var threads = forum.threads;
      var thread = threads.shift();
      buildThread(thread, function () {
        thread = threads.shift();
        if (thread) {
          buildThread(thread, this);
        } else {
          //All threads were built.
          onSuccess();
        };
      });
    });
  };

  var buildThread = function (thread, onSuccess) {
    thread.populate('posts', function(err, thread){
      if(err) throw new Error(err);
      if(!thread.posts.length) return onSuccess();

      //Build posts one by one
      var posts = thread.posts;
      var post = posts.shift();
      buildPost(post, function () {
        post = posts.shift();
        if (post) {
          buildPost(post, this);
        } else {
          //All posts were built.
          onSuccess();
        };
      });
    });
  };

  var buildPost = function (post, onSuccess) {
    post.populate('posts', function(err, post){
      if(err) throw new Error(err);
      if(!post.posts.length) return onSuccess();

      //Build comments one by one
      var posts = post.posts;
      var _post = posts.shift();
      buildPost(_post, function () {
        _post = posts.shift();
        if (_post) {
          buildPost(_post, this);
        } else {
          //All comments were built.
          onSuccess();
        };
      });
    });
  };
}; 

نصائح أخرى

This is my solution, just a minor fixs in the @Danypype solution.

exports.show = function(req, res){
  //Get the owner's forums
  Forum.find({ ownerId: req.params.owner_id }).exec(function(err, forums) {
    if(err) throw new Error(err);
    if(!forums.length) return response.json(forums); //Send an empty array if no forums where found

    //Build forums one by one
    var forum = forums.shift();  
    var responseForums = [forum];

    buildForum(forum, function () {
      forum = forums.shift();
      if (forum) {
        responseForums.push(forum);
        buildForum(forum, arguments.callee);
      } else {
        //All forums were built.
        res.json(responseForums);
      };
    });
  });

  var buildForum = function (forum, onSuccess) {
    forum.populate('threads', function(err, forum){
      if(err) throw new Error(err);
      if(!forum.threads.length) return onSuccess();
      if(forum.length == 1) return onSuccess();

      var thread = forum.threads.shift();
      var responseThreads = [thread];

      buildThread(thread, function () {
        thread = forum.threads.shift();
        if (thread) {
          responseThreads.push(thread)
          buildThread(thread, arguments.callee);
        } else {
          //All threads were built.
          forum.threads = responseThreads;
          onSuccess();
        };
      });
    });
  };

  var buildThread = function (thread, onSuccess) {
    thread.populate('posts', function(err, thread){
      if(err) throw new Error(err);
      if(!thread.posts.length) return onSuccess();

      var post = thread.posts.shift();
      var responsePosts = [post]

      buildPost(post, function () {
        post = thread.posts.shift();
        if (post) {
          responsePosts.push(post);
          buildPost(post, arguments.callee);
        } else {
          //All posts were built.
          thread.posts = responsePosts;
          onSuccess();
        };
      });
    });
  };

  var buildPost = function (post, onSuccess) {
    post.populate('posts', function(err, post){
      if(err) throw new Error(err);
      if(!post.posts.length) return onSuccess();

      //Build comments one by one
      var _post = post.posts.shift();
      var response_posts = [_post];

      buildPost(_post, function () {
        _post = post.posts.shift();
        if (_post) {
          response_posts.push(_post);
          buildPost(_post, arguments.callee);
        } else {
          //All comments were built.
          post.posts = response_posts;
          onSuccess();
        };
      });
    });
  };
};  
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top