Mongoose: Virtual getter for virtual populated

Created on 22 Dec 2016  路  4Comments  路  Source: Automattic/mongoose

I have post and vote schemes. Vote contains kind of content (can be Post or Comment, etc) and content's _id in field obj.
I created virtual population voted. Everything works as expected.

const co = require('co');
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

mongoose.connect();
mongoose.Promise = global.Promise;
mongoose.set('debug', true);

const VoteSchema = new Schema({
  value: Boolean,
  obj: String,
  kind: String,
  user: String
});

const PostSchema = new Schema({
  title: String
});

PostSchema.virtual('voted', {
  ref: 'Vote',
  localField: '_id',
  foreignField: 'obj',
  justOne: true,
});

const Vote = mongoose.model('Vote', VoteSchema);
const Post = mongoose.model('Post', PostSchema);

then I do some actions

co(function*() {

  // clear collections
  yield [
    Post.remove(),
    Vote.remove()
  ];

  // create post
  const post = new Post({
    title: 'post-1'
  });

  // create vote
  const vote = new Vote({
    value: true,
    obj: post.id,
    kind: Post.modelName,
    user: 'uid:1'
  });

  // store models to db
  yield [
    post.save(),
    vote.save()
  ];

  // populate related votes
  yield post
    .populate({path: 'voted', match: {user: 'uid:1', kind: Post.modelName}})
    .execPopulate();

}).catch(e => console.log(e));

result:

{ voted: 
   { _id: ObjectId("585bf226e481ec02c6f42143"),
     value: true,
     obj: '585bf226e481ec02c6f42142',
     kind: 'Post',
     user: 'uid:1',
     __v: 0 },
  __v: 0,
  title: 'post-1',
  _id: ObjectId("585bf226e481ec02c6f42142") }

The question is there any way to set getter over this virtual population, so I get true or false in field voted but not the object as it now?
Now I'm doing it by overwriting toJSON:

PostSchema.set('toJSON', {
  transform: (doc, ret) => {
    ret.voted = !!ret.voted;
    return ret;
  }
});

it's results as I need:

{ voted: true,
  __v: 0,
  title: 'post-1',
  _id: ObjectId("585bf30e7af9a202d34debc3") }

Most helpful comment

Mongoose supports nesting getters, here's an example:

const VoteSchema = new Schema({
  value: Boolean,
  obj: String,
  kind: String,
  user: String,
  voted: String
});

const PostSchema = new Schema({
  title: String
});

var virtual = PostSchema.virtual('voted', {
  ref: 'Vote',
  localField: '_id',
  foreignField: 'obj',
  justOne: true,
});

virtual.getters.unshift(v => !!v);

const Vote = mongoose.model('Vote', VoteSchema);
const Post = mongoose.model('Post', PostSchema);

co(function * () {
    // create post
  const post = new Post({
    title: 'post-1'
  });

  // create vote
  const vote = new Vote({
    value: true,
    obj: post.id,
    kind: Post.modelName,
    user: 'uid:1'
  });

  // store models to db
  yield [
    post.save(),
    vote.save()
  ];

  // populate related votes
  const r = yield Post.findById(post._id)
    .populate({path: 'voted', match: {user: 'uid:1', kind: Post.modelName}});

  console.log('r', r.toObject({ virtuals: true }));
  console.log(r.voted);
});

Just make sure you unshift() the getter rather than just chain .get() because mongoose executes getters in _reverse_ order.

All 4 comments

User.virtual("myVoted")
    .get(function getVoted() {
        return this.voted.value;
    });

Mongoose supports nesting getters, here's an example:

const VoteSchema = new Schema({
  value: Boolean,
  obj: String,
  kind: String,
  user: String,
  voted: String
});

const PostSchema = new Schema({
  title: String
});

var virtual = PostSchema.virtual('voted', {
  ref: 'Vote',
  localField: '_id',
  foreignField: 'obj',
  justOne: true,
});

virtual.getters.unshift(v => !!v);

const Vote = mongoose.model('Vote', VoteSchema);
const Post = mongoose.model('Post', PostSchema);

co(function * () {
    // create post
  const post = new Post({
    title: 'post-1'
  });

  // create vote
  const vote = new Vote({
    value: true,
    obj: post.id,
    kind: Post.modelName,
    user: 'uid:1'
  });

  // store models to db
  yield [
    post.save(),
    vote.save()
  ];

  // populate related votes
  const r = yield Post.findById(post._id)
    .populate({path: 'voted', match: {user: 'uid:1', kind: Post.modelName}});

  console.log('r', r.toObject({ virtuals: true }));
  console.log(r.voted);
});

Just make sure you unshift() the getter rather than just chain .get() because mongoose executes getters in _reverse_ order.

Oh!! This is exactly what I was looking for. I saw stacked getters but didn't know how to change the order ^_^. Thank you very much.

Yeah I wasn't quite aware of that myself. Not well documented at all but it works. Just be wary, it won't be reverse order anymore in 5.0, follow #4835 for updates

Was this page helpful?
0 / 5 - 0 ratings