简体   繁体   中英

How do I populate sub-document after $geoNear

I am using NodeJs and Mongoose and building a feature to list near by deals.

   Deal.db.db.command({
    "geoNear": Deal.collection.name,
    "near": [23,67],
    "spherical": true,
    "distanceField": "dis"
   }, function (err, documents) {
    if (err) {
      return next(err);
    }
    console.log(documents);
  });

Deal schema:

var dealSchema = new mongoose.Schema({
  title: String,
  merchant: {
    type: mongoose.Schema.Types.ObjectId,
    ref: Merchant,
    index: true
  }
});

Here, I get all deals and their distances from current location. Inside Deal schema I have a merchant as reference object.

How do I populate merchants with each returned Deal object? Do I need to iterate through all returned Deal objects and populate manually?

This actually turns out to be kind of interesting. Certainly what you do not want to really do is "dive into" the native driver part, though you possibly could on solution 2. Which leads to that there are a few ways to go about this without actually rolling your own queries to match this manually.

First a little setup. Rather than reproduce your dataset I have just picked something I had lying around from previous testing. The principles are the same, so a basic setup:

var mongoose = require('mongoose'),
    async = require('async'),
    Schema = mongoose.Schema;

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

var infoSchema = new Schema({
  "description": String
});

var shapeSchema = new Schema({
  "_id": String,
  "amenity": String,
  "shape": {
    "type": { "type": String },
    "coordinates": []
  },
  "info": { "type": Schema.Types.ObjectId, "ref": "Info" }
});

var Shape = mongoose.model( "Shape", shapeSchema );
var Info = mongoose.model( "Info", infoSchema );

Essentially one model with the "geo" information and the other that is referenced with just some information we want to populate. Also my lazy alteration of data:

{ 
    "_id" : "P1", 
    "amenity" : "restaurant", 
    "shape" : { "type" : "Point", "coordinates" : [ 2, 2 ] }
}
{ 
    "_id" : "P3",
    "amenity" : "police",
    "shape" : { "type" : "Point", "coordinates" : [ 4, 2 ] }
}
{ 
    "_id" : "P4",
    "amenity" : "police",
    "shape" : { "type" : "Point", "coordinates" : [ 4, 4 ] }
}
{ 
    "_id" : "P2", 
    "amenity" : "restaurant",
    "shape" : { "type" : "Point", "coordinates" : [ 2, 4 ] },
    "info" : ObjectId("539b90543249ff8d18e863fb")
}

So there is one thing in there that we can expect to populate. As it turns out a $near query is pretty straightforward as does sort as expected:

var query = Shape.find(
  {
    "shape": {
      "$near": {
        "$geometry": {
          "type": "Point",
          "coordinates": [ 2, 4 ]
        }
      }
    }
  }
);

query.populate("info").exec(function(err,shapes) {
    if (err) throw err;
    console.log( shapes );
});

That is just a standard .find() with the $near operator invoked. This is MongoDB 2.6 syntax, so there is that. But the results sort correctly and populate as expected:

[ 
    { _id: 'P2',
      amenity: 'restaurant',
      info:
      { _id: 539b90543249ff8d18e863fb,
        description: 'Jamies Restaurant',
        __v: 0 },
      shape: { type: 'Point', coordinates: [ 2, 4 ] } },
    { _id: 'P4',
       amenity: 'police',
       info: null,
       shape: { type: 'Point', coordinates: [ 4, 4 ] } },
    { _id: 'P1',
      amenity: 'restaurant',
      info: null,
      shape: { type: 'Point', coordinates: [ 2, 2 ] } },
    { _id: 'P3',
      amenity: 'police',
      info: null,
      shape: { type: 'Point', coordinates: [ 4, 2 ] } }
]

That is pretty nice, and a simple way to invoke. The catch? Sadly changing the operator to $geoNear to take the spherical geometry into account starts throwing errors. So if you want that then you can't just do things as you are used to.

Another approach though is that mongoose has a .geoNear() function that is supported. But just like the db.command invocation, this does not return a mongoose document or other Model type object that will accept .populate() . Solution? Just play with the output a little:

var query = Shape.geoNear({
  "type": "Point",
  "coordinates": [ 2, 4 ]
},{spherical: true},function(err,shapes) {
  if (err) throw err;

  shapes = shapes.map(function(x) {
    delete x.dis;
    var a = new Shape( x.obj );

    return a;
  });
  Shape.populate( shapes, { path: "info" }, function(err,docs) {
    if (err) throw err;

    console.log( docs );

  });

So here the result returned is an array of raw objects. But with a little manipulation you can turn these into something that is going to work with the .populate() method which can also be invoked from the model class as shown.

The result of course is the same, though the field order may be a little different. And you didn't need to iterate an call the queries yourself. This is really all that .populate() is actually doing, but I think we can agree that using the .populate() method at least looks a lot cleaner and does not re-invent the wheel for this purpose.

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