简体   繁体   中英

Understanding Mongoose sub documents

I'm learning Mongoose. At the moment I did few nice things but I really don't understand exactly how Mongoose manage relationships between Schemas.

So, easy thing (I hope): I'm doing a classic exercise (by my self because I cannot find a good tutorial that create more than 2 Schemas) with 3 Schemas:

User, Post, Comment.

  • User can create many Post;
  • User can create many Comment;
  • Post belong to User.
  • Comment belong to User and Post.

I don't think it is something very hard uhu?

At the moment I can manage very well Relation between User and Post. My Unit test return exactly what I need, at the moment I'm using mongo-relation and I don't know if it is a good idea...

  it('Use should create a Post', function(done) {
    User.findOne({ email: 'test@email.com' }, function(err, user) {
      var post = {
        title: 'Post title',
        message: 'Post message',
        comments: []
      };
      user.posts.create(post, function(err, user, post) {
        if (err) return done(err);
        user.posts[0].should.equal(post._id);
        post.author.should.equal(user._id);
        // etc...
        done();
      });
    });
  });

The problem now is to create a comment. I can not create a comment that refere to the Post and to the User together.

I did something like that and works but when I perform a remove it is removed only from the Post and not from the User.

So I think there is something I miss or I still need to study to enhance it.

  it('User should add a Comment to a Post', function(done) {
    User.findOne({ email: 'test@email.com' }, function(err, user) {
      if (err) return done(err);

      var comment = new Comment({
        author: user._id,
        message: 'Post comment'
      });

      Post.findOne({ title: 'Post title'}, function(err, post) {
        if (err) return done(err);
        post.comments.append(comment, function(err, comment) {
          if (err) return done(err);

          post.save(function(err) {
            if (err) return done(err);
          });

          comment.author.should.equal(user._id);
          post.comments.should.have.length(1);
          // etc...
          done();
        });
      });
    });
  });

As you can see the code is not very "nice to see" but it works fine in terms of creations.

The problem is when I remove a Comment. It seems like something is wrong.

Here is the Model relationship:

// User Schema
var userSchema = new mongoose.Schema({
  // [...],

  posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }],
  comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],

});

// Post Schema
var postSchema = new mongoose.Schema({
    author: { type: mongoose.Schema.ObjectId, ref: 'User', refPath: 'posts' },
    title: String,
    message: String,
    comments: [{ type: mongoose.Schema.ObjectId, ref: 'Comment' }]
});

// Comment Schema
var commentSchema = new mongoose.Schema({
    author: { type: mongoose.Schema.ObjectId, ref: 'User', refPath: 'comments' },
    post: { type: mongoose.Schema.ObjectId, ref: 'Post', refPath: 'comments' },
    message: String
});

I really hope in your help to understand all this.

It will be nice also a simple good tutorial about it.

I think you are misunderstanding subdocuments. The way you have your schema setup you are creating references to documents in other collections.

For example if you create a post, in the database it will look like this:

{
  "author": ObjectId(123),
  "title": "post title",
  "message": "post message",
  "comments": [ObjectId(456), ObjectId(789)]
}

Notice the "author" field just contains the ID of the author who created it. It does not actually contain the document itself.

When you read the document from the DB you can use the mongoose 'populate' functionality to also fetch the referred to document.

Ex (with populate):

Post
  .findOne({ title: 'Post title'})
  .populate('author', function(err, post) {
    // this will print out the whole user object
    console.log(post.author)
  });

Ex (no populate):

Post
  .findOne({ title: 'Post title'}, function(err, post) {
    // this will print out the object ID
    console.log(post.author)
  });

Subdocuments:

You can actually nest data in the DB using subdocuments, the schema would look slightly different:

 var postSchema = new mongoose.Schema({
   author: { userSchema },
   title: String,
   message: String,
   comments: [commentSchema]
 });

When saving a post the user document would be nested inside the post:

{
  "author": { 
    "name": "user name",
    "email": "test@email.com"
    ...
  },
  "title": "post title",
  "message": "post message",
  "comments": [{
     "message": "test",
     ...
  }, {
     "message": "test",
     ...
  }]
}

Subdocuments can be useful in mongo, but probably not for this case because you would be duplicating all of the user data in every post.

Removing documents

When you issue a Comment.remove(id) the comment will be removed but it will not affect the other documents referring to it. So you will then have a Post and a User with a comment ID that does not exist. You need to manually clean up the comment ID from the other documents. You could use the mongoose pre remove event to do this. http://mongoosejs.com/docs/middleware.html

commentSchema.pre('remove', function (next) {
  // this refers to the document being removed
  var userId = this.author;
  var postId = this.post;

  User.findById(userId, function(err, user) {

    // remove comment id from users.comments here;

    Post.findById(postId, function(err, post) {

      // remove comment id from post.comments;

      next();
    });
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM