Mongoose Activity model with dynamic reference
Asked Answered
S

5

10

I want to create an activity model in order to show a timeline kind of thing with my application, but I don't know exactly how to dynamically reference a collection in a mongoose schema. Im using mongoose (3.6.20)

In what I have so far the actor is always a user, but the _object can be a user or a post.

This is kind of what I have:

userSchema = new Schema({
    _id: ObjectId;
    name: String
});

postSchema = new Schema({
    _id: ObjectId;
    author_id: String, ref: User
});

What I want to do is:

activitySchema = new Schema({
    _object: { String: Id of the thing, ref:'can be user or post'} // Object that the verb occurred on... can be a post or a user
    actor: { type: String, ref: 'User' },
    action: { type: String, default: ""}, //ex: has replied to:, or started following:
});

How can I solve this using dynamic references with mongoose if possible, and how will I populate it?

Thank you!

Sanford answered 28/10, 2013 at 23:31 Comment(0)
P
6

Why not acitivitySchema have both?

activitySchema = new Schema({
    user:{ type: Schema.Types.ObjectId, ref:'User' }, 
    post:{ type: Schema.Types.ObjectId, ref:'Post' }, 
    actor: { type: String, ref: 'User' },
    action: { type: String, default: ""}, //ex: has replied to:, or started following:
});

When you get the mongoose object, just call populate('user') and populate('post'). It will fill the user or post based on what was saved. Mongodb is extremely efficient in doing this.

Pozzy answered 29/10, 2013 at 0:33 Comment(0)
C
33

updated 2017..

you can now have dynamic refs using refPath or even multiple dynamic refs.

    var userSchema = new Schema({
    name: String,
    connections: [{
        kind: String,
        item: { type: ObjectId, refPath: 'connections.kind' }
        }]
    }); 

The refPath property above means that mongoose will look at the connections.kind path to determine which model to use for populate(). In other words, the refPath property enables you to make the ref property dynamic.

http://mongoosejs.com/docs/populate.html Dynamic References

Candleberry answered 16/6, 2017 at 5:7 Comment(2)
This must be the answer!Boonie
lifesaver, God bless uTical
A
10

Here's a pattern for how you could define the schemas if you wanted to not have multiple properties for each type of model that might be stored in _object:

var UserSchema = new Schema({
    name: String
});

var PostSchema = new Schema({
    author: { type: Schema.Types.ObjectId, ref: 'User' }
});

var ActivitySchema = new Schema({
    _object : Schema.Types.ObjectId,
    actor: { type: Schema.Types.ObjectId, ref: 'User' },
    action: { type: String, default: "" }
});

var models = {
    User : mongoose.model('User', UserSchema),
    Post : mongoose.model('Post', PostSchema),
    Activity : mongoose.model('Activity', ActivitySchema)
};
  1. Create the UserSchema. You don't need to define the _id field as it's automatically created for you as an ObjectId.
  2. Define the PostSchema and reference a User instance by using ref. The type is an ObjectId (which is the way the models_id`s are defined by default).
  3. Define the ActivitySchema. In this case, don't define the ref type as it's not known. Just store the core ObjectId type.

When you want to create an Activity instance, it's not much different:

var activity = new models.Activity( {
   _object: post._id,
   actor: user._id,
   action: "post"
});

You always use the model._id syntax anyway.

Also, you can use populate if you know the type of the object. For example, maybe a post action means the _object contains a User:

models.Activity.find({action: 'post'}).populate({
   path: '_object'
   , select: 'name'
   , model: 'User'
}).exec(function (err, activities) {
   console.log(activities);
})
Ambie answered 29/10, 2013 at 0:30 Comment(0)
P
6

Why not acitivitySchema have both?

activitySchema = new Schema({
    user:{ type: Schema.Types.ObjectId, ref:'User' }, 
    post:{ type: Schema.Types.ObjectId, ref:'Post' }, 
    actor: { type: String, ref: 'User' },
    action: { type: String, default: ""}, //ex: has replied to:, or started following:
});

When you get the mongoose object, just call populate('user') and populate('post'). It will fill the user or post based on what was saved. Mongodb is extremely efficient in doing this.

Pozzy answered 29/10, 2013 at 0:33 Comment(0)
A
2

If you want mongoose populate to work, and there's a finite number of schema variations, you need to do a "sparse model":

activitySchema = new Schema({
    user: {type: ObjectId, ref: 'User'},
    post: {type: ObjectId, ref: 'Post'}
    actor: { type: String, ref: 'User' },
    action: { type: String, default: ""}, //ex: has replied to:, or started following:
});

That will work fine and you just have to code to understand that 1 of those will be null. It is also really easy to query just 1 flavor of these since the other flavors just won't match the query.

But otherwise, you can't use mongoose populate, however, sticking modelId and modelName in your activity schema and then loading the corresponding model document is on the order of 3 lines of code, so don't sweat writing something to do that if you really like using a single field that can refer to varying collections.

Avelinaaveline answered 29/10, 2013 at 0:35 Comment(2)
Looks like we both answered the exact same thing at same time. :)Pozzy
lol yeah you both did, and both answers are correct, but Im giving the answer to @PradeepMahdevu ;) Thank you Both.Sanford
H
0

Based on how I like to build activity streams, the "post" as many are using here is what's dynamic and therefore an addtional property is needed if you want variations.

activitySchema = new Schema({
  actor: { 
    type: String, 
    ref: 'User' 
  },
  user: { 
    type: ObjectId, 
    ref: 'User'
  },
  objectType: { 
    type: String, 
    enum: ['Video', 'Image', 'Comment', 'Message', 'etc']
  },
  object: {
    type: ObjectId 
  }
})

This then allows you to use objectType as the ref when calling populate() on each activity.

Houdan answered 30/6 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.