Question

I have a collections of Users and collection of Posts.

var PostSchema = new Schema({
  title: {type : String, default : '', trim : true},
  description: {type : String, default : '', trim : true},
  user: {type : Schema.ObjectId, ref : 'User'},
  slug: {type: String, trim: true, lowercase: true},
  createdAt  : {type : Date, default : Date.now}
})

How do I find all documents of PostSchema that belong to a particular user? Also, is it possible to find all the Posts of the user by user.username field?

Was it helpful?

Solution

This is how i would do it, the easy (read: noob) way :p.

Get posts by given userId:

var PostSchema  =   new Schema({...});

//  allows you to call method findByUser() of the model of posts
//  with parameters: the user_id and the callback function (fn).
PostSchema.statics.findByUser    =   function(userId, fn) {
    this.find({user:userId}, fn);
};

//  we create the posts model.
posts       =   mongoose.model('posts', PostSchema),

//  example usage: find the posts from 'some_user_id' or '2a1a9a...'.
posts.findByUser(some_user_id || '2a1a9a8a3a2a1a9a8a32a1a3', function(err, docs) {
    //  ensure there are no errors and there were docs given.
    if (!err && docs) {
        //  will return the posts.
        console.log(docs);
    }
});

Get posts by username (of schema user): Same method can be applied, but we first search what the id of the username is in collection users.

//  the method we are going to make is dependent on the model/collection 'users',
var users       =   mongoose.model('users', UserSchema);

//  we create another static method on the schema of 'posts'.
PostSchema.statics.findByName = function(username, fn) {
    //  we search the user with username 'username'
    //  in model 'users'.
    //  Here, we also are assuming that username = unique.
    this.model('users').findOne({username:username}, function(err, doc) {
        //  ensure there are no errors and there were docs given.
        if (!err && doc) {
            //  we will use the statics we made above.
            //  i have a feeling i lose the context here, so i refer to this.model('posts'),
            //  maybe this.findByUser() als works; i could not test my code.
            this.model('posts').findByUser(doc._id, fn);
        }
        //  if there is a failure, quit and call fn.
        else fn(err, doc);
    });
};

These are 'quick' solutions to your questions. I think it could be done much more effective/better.

For you decision to include posts in the user or separately, here's my opinion: It's a coding principle to make your code future-proof. Think about when the database gets humongous (=where the name mongo is derived from), where ONE user has over 1,000,000 posts.. So everytime you want to fetch only the userdata, you also get 1,000,000 post documents alltogether. So i think it's better to separate the collections to prevent this.

old: getter to fetch user document from post.

You can make a method available to all of your docs something like this:

PostSchema.methods.getUser = function(callback) {
    // be sure you have the mongoose object.
    mongoose.model('users').findById(this.user, callback);
};

So you can just call the method:

postDoc.getUser();

Wrote this on the top of my head, so it's more pseudo-code. More info here: http://mongoosejs.com/docs/guide.html

OTHER TIPS

Given a bit of basic setup:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

mongoose.connect('mongodb://localhost/nodetest');

var userSchema = new Schema({
  name: { type: String, default: '', trim: true }
});

var postSchema = new Schema({
  title: { type: String, default: '', trim: true },
  description:  { type: String, default: '', trim: true },
  user: { type: Schema.ObjectId, ref: 'User' },
  slug: { type: String, trim: true, lowercase: true },
  createdAt: { type: Date, default: Date.now }
});

var User = mongoose.model( 'User', userSchema );

var Post = mongoose.model( 'Post', postSchema );

And some data for users:

{ "_id" : ObjectId("536c6bf6da1ee15a4c0d7dc6"), "name" : "Neil", "__v" : 0 }
{ "_id" : ObjectId("536c6e338534d6a6afd41f64"), "name" : "Bill" }

And posts:

{
    "_id" : ObjectId("536c6bf6da1ee15a4c0d7dc7"),
    "user" : ObjectId("536c6bf6da1ee15a4c0d7dc6"),
    "slug" : "this_post",
    "createdAt" : ISODate("2014-05-09T05:47:34.249Z"),
    "description" : "another post",
    "title" : "this post",
    "__v" : 0
}
{
    "_id" : ObjectId("536c6e878534d6a6afd41f65"),
    "user" : ObjectId("536c6e338534d6a6afd41f64"),
    "createdAt" : ISODate("2014-05-09T05:58:31.642Z"),
    "slug" : "this_here",
    "description" : "This here",
    "title" : "My Post"
}

Finding the user by Id is a simple query:

Post.findOne({ user: "536c6bf6da1ee15a4c0d7dc6"  },function(err,doc) {
  console.log( doc );
});

Finding the user by the "name", is a bit different

Post.find({})
  .populate('user', null, { name: 'Neil' })
  .exec(function(err,posts) {
    posts = posts.filter(function(post) {
      return ( post.user != null );
    });

    console.log( posts );

  });

So note the use of filter here. The actually querying will actually match everything in Posts and the usage of populate is just conditionally "filling" the matching gaps. We altered that query just to return the name you want.

But it is the filter here that reduces the results. Noting that this is happening "client" side and is not "joining" in any way on the server.

So there is the limitation. If you want to truly restrict, then use embedded documents. If embedding is not possible you will need a another reference somehow in order to search the "User" to find their "Posts", and then retrieve the posts.

It's all a matter of how you approach the problem to best suit you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top