Is it possible to have mongoose populate a Mixed field only if the field contains an ObjectId?

限于喜欢 提交于 2020-01-30 05:24:28

问题


Setup

Let's say we have schemas for Foo and Bar. Foo has a field bar. This field can either contain an ObjectId that reference a document Bar, or it could contain other arbitrary objects that are not ObjectIds.

const fooSchema = new mongoose.Schema({
  bar: {
    type: mongoose.Schema.Types.Mixed,
    ref: 'Bar'
  }
});
const Foo = <any>mongoose.model<any>('Foo', fooSchema);

const barSchema = new mongoose.Schema({
  name: String
});
const Bar = <any>mongoose.model<any>('Bar', barSchema);

Problem

Now suppose we have a bunch of Foo documents.

I would like to be able to use mongoose's populate on the bar field to automatically replace references to a Bar document with the actual Bar document itself. I would also like to leave all other objects that are not references to a Bar document unchanged.

Normally, I would use something like this to get all the Foo documents and then populate the bar field:

Foo.find().populate('bar')

However, this method will throw an exception when it encounters objects in the bar field that are not ObjectIds, as opposed to leaving them untouched.

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): CastError: Cast to ObjectId failed for value "Some arbitrary object" at path "_id" for model "Bar"

Attempt at finding a solution

I have examined using the match option on populate, by requiring that a field on Bar exists:

Foo.find().populate({
  path: 'bar',
  match: {
    name: {
      $exists: true
    }
  }
}

Unfortunately, the error is the same.

Question

So my question is then, is there any way to get mongoose to only populate a field if the field contains an ObjectId, and leave it alone otherwise?


回答1:


As far as I know you cannot use populate that way. Select property works after trying to get values for population and there's no way to filter it before that.

You would have to do it manually. You could do it manually.

let foos = await Foo.find({});
foos = foos.map(function (f) {
  return new Promise(function (resolve) {
    if (condition()) {
        Foo.populate(f, {path: 'bar'}).then(function(populatedF){
            resolve(f);
        });
    } else {
      resolve(f);
    }
  });
});

await Promise.all(foos).then(function (fs) {
  res.status(200).json(fs);
});

Elegantly would be to wrap it in post hook or static method on your Model.

Another option would be to send 2 queries:

const foosPopulated = Foo.find({ alma: { $type: 2 } }).populate('bar'); // type of string
const foosNotPopulated = Foo.find({ alma: { $type: 3 } }); // type of object

const foos = foosPopulated.concat(foosNotPopulated);

This is of course suboptimal because of 2 queries (and all population queries) but maybe this will not be a problem for you. Readability is much better. Of course you could then change find queries to match your case specifically.



来源:https://stackoverflow.com/questions/46373989/is-it-possible-to-have-mongoose-populate-a-mixed-field-only-if-the-field-contain

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!