Mongoose populate pass found object reference key

I have a mongoose group diagram that contains a prompt (an array of supporting document) and currentMove, the prompt also contains currentMove, and I want to get a document with only a subdocument that has the same currentMove.

Group.findById("5a03fa29fafa645c8a399353")
.populate({
    path: 'invitee.user_id',
    select: 'currentMove',
    model:"User",
     match: {
         "currentMove":{
            $eq: "$currentMove"
        }
    }
}) 

This generates an unknown currentMove object identifier to match. I'm not sure if the mongoose has such functionality. Can someone help me please?

+4
source share
1 answer

MongoDB $lookup .populate(). , , - , MongoDB , , .populate().

, .populate(), , Model.populate() $where Array.map(), , "" .

, . $lookup - , "" "" , , .

"" , , , . :

const userSchema = new Schema({
  name: String,
  currentMove: Number
})

const groupSchema = new Schema({
  name: String,
  topic: String,
  currentMove: Number,
  invitee: [{
    user_id: { type: Schema.Types.ObjectId, ref: 'User' },
    confirmed: { type: Boolean, default: false }
  }]
});

$lookup $group

$lookup. $unwind , $lookup. , "" , , "", BSON (16 ) :

  Group.aggregate([
    { "$unwind": "$invitee" },
    { "$lookup": {
      "from": User.collection.name,
      "localField": "invitee.user_id",
      "foreignField": "_id",
      "as": "invitee.user_id"
    }},
    { "$unwind": "$invitee.user_id" },
    { "$redact": {
      "$cond": {
        "if": { "$eq": ["$currentMove", "$invitee.user_id.currentMove"] },
        "then": "$$KEEP",
        "else": "$$PRUNE"
      }
    }},
    { "$group": {
      "_id": "$_id",
      "name": { "$first": "$name" },
      "topic": { "$first": "$topic" },
      "currentMove": { "$first": "$currentMove" },
      "invitee": { "$push": "$invitee" }
    }}
  ]);

$redact, $lookup. "currentMove" , "" User.

$unwind , $group $push, ( ) , $first.

, . Mongoose. , , , .

$

, , "" "" , BSON, , "" , $map $filter :

  Group.aggregate([
    { "$lookup": {
      "from": User.collection.name,
      "localField": "invitee.user_id",
      "foreignField": "_id",
      "as": "inviteeT"
    }},
    { "$addFields": {
      "invitee": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$inviteeT",
              "as": "i",
              "cond": { "$eq": ["$$i.currentMove","$currentMove"] }
            }
          },
          "as": "i",
          "in": {
            "_id": {
              "$arrayElemAt": [
                "$invitee._id",
                { "$indexOfArray": ["$invitee.user_id", "$$i._id"] }
              ]
            },
            "user_id": "$$i",
            "confirmed": {
              "$arrayElemAt": [
                "$invitee.confirmed",
                { "$indexOfArray": ["$invitee.user_id","$$i._id"] }
              ]
            }
          }
        }
      }
    }},
    { "$project": { "inviteeT": 0 } },
    { "$match": { "invitee.0": { "$exists": true } } }
  ]);

$redact, "", $filter , "inviteeT", "currentMove". "" , "" , $map .

"" , $arrayElemAt $indexOfArray. $indexOfArray "_id" "user_id" "index" . , , $lookup .

"index" $arrayElemAt "" "$invitee.confirmed" , . "" .

, "inviteeT", "invitee" $addFields. , - $project "" . , , $unwind "filter", - . , $match $exists 0, , , " " .

- MongoDB 3.6 "

MongoDB 3.6 $lookup "" , "localField" "foreignField".

  Group.aggregate([
    { "$lookup": {
      "from": User.collection.name,
      "let": {
        "ids": "$invitee._id",
        "users": "$invitee.user_id",
        "confirmed": "$invitee.confirmed",
        "currentMove": "$currentMove"
      },
      "pipeline": [
        { "$match": {
          "$expr": {
            "$and": [
              { "$in": ["$_id", "$$users"] },
              { "$eq": ["$currentMove", "$$currentMove"] }
            ]
          }
        }},
        { "$project": {
          "_id": {
            "$arrayElemAt": [
              "$$ids",
              { "$indexOfArray": ["$$users", "$_id"] }
            ]
          },
          "user_id": "$$ROOT",
          "confirmed": {
            "$arrayElemAt": [
              "$$confirmed",
              { "$indexOfArray": ["$$users", "$_id"] }
            ]
          }
        }}
      ],
      "as": "invitee"
    }},
    { "$match": { "invitee.0": { "$exists": true } } }
  ])

, "" - , "let". , , , , , .

"let" "" , "pipeline", , , .

$expr $redact $filter, , "" "" , ​​. "$invitee.user_id" , "$$users" .

$in , , "" "". , " ".

"", $project $match, . , "" , . "" , , "" $lookup, .

, , , "" , , , . , $exists.


, , , $lookup , , , . , "", .populate() , , .

, . MongoDB 3.6, .

Node.js v8.x async/await ( ), , LTS , . , :)

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

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

const uri = 'mongodb://localhost/rollgroup',
      options = { useMongoClient: true };

const userSchema = new Schema({
  name: String,
  currentMove: Number
})

const groupSchema = new Schema({
  name: String,
  topic: String,
  currentMove: Number,
  invitee: [{
    user_id: { type: Schema.Types.ObjectId, ref: 'User' },
    confirmed: { type: Boolean, default: false }
  }]
});

const User = mongoose.model('User', userSchema);
const Group = mongoose.model('Group', groupSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

    let { version } = await conn.db.admin().command({'buildInfo': 1});

    // Clean data
    await Promise.all(
      Object.entries(conn.models).map(([k,m]) => m.remove() )
    );

    // Add some users
    let users = await User.insertMany([
      { name: 'Bill', currentMove: 1 },
      { name: 'Ted', currentMove: 2 },
      { name: 'Fred', currentMove: 3 },
      { name: 'Sally', currentMove: 4 },
      { name: 'Harry', currentMove: 5 }
    ]);

    await Group.create({
      name: 'Group1',
      topic: 'This stuff',
      currentMove: 3,
      invitee: users.map( u =>
        ({ user_id: u._id, confirmed: (u.currentMove === 3) })
      )
    });

    await (async function() {
      console.log('Unwinding example');
      let result = await Group.aggregate([
        { "$unwind": "$invitee" },
        { "$lookup": {
          "from": User.collection.name,
          "localField": "invitee.user_id",
          "foreignField": "_id",
          "as": "invitee.user_id"
        }},
        { "$unwind": "$invitee.user_id" },
        { "$redact": {
          "$cond": {
            "if": { "$eq": ["$currentMove", "$invitee.user_id.currentMove"] },
            "then": "$$KEEP",
            "else": "$$PRUNE"
          }
        }},
        { "$group": {
          "_id": "$_id",
          "name": { "$first": "$name" },
          "topic": { "$first": "$topic" },
          "currentMove": { "$first": "$currentMove" },
          "invitee": { "$push": "$invitee" }
        }}
      ]);

      log(result);

    })();

    await (async function() {
      console.log('Using $filter example');
      let result = await Group.aggregate([
        { "$lookup": {
          "from": User.collection.name,
          "localField": "invitee.user_id",
          "foreignField": "_id",
          "as": "inviteeT"
        }},
        { "$addFields": {
          "invitee": {
            "$map": {
              "input": {
                "$filter": {
                  "input": "$inviteeT",
                  "as": "i",
                  "cond": { "$eq": ["$$i.currentMove","$currentMove"] }
                }
              },
              "as": "i",
              "in": {
                "_id": {
                  "$arrayElemAt": [
                    "$invitee._id",
                    { "$indexOfArray": ["$invitee.user_id", "$$i._id"] }
                  ]
                },
                "user_id": "$$i",
                "confirmed": {
                  "$arrayElemAt": [
                    "$invitee.confirmed",
                    { "$indexOfArray": ["$invitee.user_id","$$i._id"] }
                  ]
                }
              }
            }
          }
        }},
        { "$project": { "inviteeT": 0 } },
        { "$match": { "invitee.0": { "$exists": true } } }
      ]);

      log(result);

    })();

    await (async function() {
      if (parseFloat(version.match(/\d\.\d/)[0]) >= 3.6) {
        console.log('New $lookup example. Yay!');
        let result = await Group.collection.aggregate([
          { "$lookup": {
            "from": User.collection.name,
            "let": {
              "ids": "$invitee._id",
              "users": "$invitee.user_id",
              "confirmed": "$invitee.confirmed",
              "currentMove": "$currentMove"
            },
            "pipeline": [
              { "$match": {
                "$expr": {
                  "$and": [
                    { "$in": ["$_id", "$$users"] },
                    { "$eq": ["$currentMove", "$$currentMove"] }
                  ]
                }
              }},
              { "$project": {
                "_id": {
                  "$arrayElemAt": [
                    "$$ids",
                    { "$indexOfArray": ["$$users", "$_id"] }
                  ]
                },
                "user_id": "$$ROOT",
                "confirmed": {
                  "$arrayElemAt": [
                    "$$confirmed",
                    { "$indexOfArray": ["$$users", "$_id"] }
                  ]
                }
              }}
            ],
            "as": "invitee"
          }},
          { "$match": { "invitee.0": { "$exists": true } } }
        ]).toArray();

        log(result);

      }
    })();

    await (async function() {
      console.log("Horrible populate example :(");

      let results = await Group.find();

       results = await Promise.all(
        results.map( r =>
          User.populate(r,{
            path: 'invitee.user_id',
            match: { "$where": `this.currentMove === ${r.currentMove}` }
          })
        )
      );

      console.log("All members still there");
      log(results);

      // Then we clean it for null values

      results = results.map( r =>
        Object.assign(r,{
          invitee: r.invitee.filter(i => i.user_id !== null)
        })
      );

      console.log("Now they are filtered");
      log(results);

    })();


  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})()

:

Mongoose: users.remove({}, {})
Mongoose: groups.remove({}, {})
Mongoose: users.insertMany([ { __v: 0, name: 'Bill', currentMove: 1, _id: 5a0afda01643cf41789e500a }, { __v: 0, name: 'Ted', currentMove: 2, _id: 5a0afda01643cf41789e500b }, { __v: 0, name: 'Fred', currentMove: 3, _id: 5a0afda01643cf41789e500c }, { __v: 0, name: 'Sally', currentMove: 4, _id: 5a0afda01643cf41789e500d }, { __v: 0, name: 'Harry', currentMove: 5, _id: 5a0afda01643cf41789e500e } ], {})
Mongoose: groups.insert({ name: 'Group1', topic: 'This stuff', currentMove: 3, _id: ObjectId("5a0afda01643cf41789e500f"), invitee: [ { user_id: ObjectId("5a0afda01643cf41789e500a"), _id: ObjectId("5a0afda01643cf41789e5014"), confirmed: false }, { user_id: ObjectId("5a0afda01643cf41789e500b"), _id: ObjectId("5a0afda01643cf41789e5013"), confirmed: false }, { user_id: ObjectId("5a0afda01643cf41789e500c"), _id: ObjectId("5a0afda01643cf41789e5012"), confirmed: true }, { user_id: ObjectId("5a0afda01643cf41789e500d"), _id: ObjectId("5a0afda01643cf41789e5011"), confirmed: false }, { user_id: ObjectId("5a0afda01643cf41789e500e"), _id: ObjectId("5a0afda01643cf41789e5010"), confirmed: false } ], __v: 0 })
Unwinding example
Mongoose: groups.aggregate([ { '$unwind': '$invitee' }, { '$lookup': { from: 'users', localField: 'invitee.user_id', foreignField: '_id', as: 'invitee.user_id' } }, { '$unwind': '$invitee.user_id' }, { '$redact': { '$cond': { if: { '$eq': [ '$currentMove', '$invitee.user_id.currentMove' ] }, then: '$$KEEP', else: '$$PRUNE' } } }, { '$group': { _id: '$_id', name: { '$first': '$name' }, topic: { '$first': '$topic' }, currentMove: { '$first': '$currentMove' }, invitee: { '$push': '$invitee' } } } ], {})
[
  {
    "_id": "5a0afda01643cf41789e500f",
    "name": "Group1",
    "topic": "This stuff",
    "currentMove": 3,
    "invitee": [
      {
        "user_id": {
          "_id": "5a0afda01643cf41789e500c",
          "__v": 0,
          "name": "Fred",
          "currentMove": 3
        },
        "_id": "5a0afda01643cf41789e5012",
        "confirmed": true
      }
    ]
  }
]
Using $filter example
Mongoose: groups.aggregate([ { '$lookup': { from: 'users', localField: 'invitee.user_id', foreignField: '_id', as: 'inviteeT' } }, { '$addFields': { invitee: { '$map': { input: { '$filter': { input: '$inviteeT', as: 'i', cond: { '$eq': [ '$$i.currentMove', '$currentMove' ] } } }, as: 'i', in: { _id: { '$arrayElemAt': [ '$invitee._id', { '$indexOfArray': [ '$invitee.user_id', '$$i._id' ] } ] }, user_id: '$$i', confirmed: { '$arrayElemAt': [ '$invitee.confirmed', { '$indexOfArray': [ '$invitee.user_id', '$$i._id' ] } ] } } } } } }, { '$project': { inviteeT: 0 } }, { '$match': { 'invitee.0': { '$exists': true } } } ], {})
[
  {
    "_id": "5a0afda01643cf41789e500f",
    "name": "Group1",
    "topic": "This stuff",
    "currentMove": 3,
    "invitee": [
      {
        "_id": "5a0afda01643cf41789e5012",
        "user_id": {
          "_id": "5a0afda01643cf41789e500c",
          "__v": 0,
          "name": "Fred",
          "currentMove": 3
        },
        "confirmed": true
      }
    ],
    "__v": 0
  }
]
New $lookup example. Yay!
Mongoose: groups.aggregate([ { '$lookup': { from: 'users', let: { ids: '$invitee._id', users: '$invitee.user_id', confirmed: '$invitee.confirmed', currentMove: '$currentMove' }, pipeline: [ { '$match': { '$expr': { '$and': [ { '$in': [ '$_id', '$$users' ] }, { '$eq': [ '$currentMove', '$$currentMove' ] } ] } } }, { '$project': { _id: { '$arrayElemAt': [ '$$ids', { '$indexOfArray': [ '$$users', '$_id' ] } ] }, user_id: '$$ROOT', confirmed: { '$arrayElemAt': [ '$$confirmed', { '$indexOfArray': [ '$$users', '$_id' ] } ] } } } ], as: 'invitee' } }, { '$match': { 'invitee.0': { '$exists': true } } } ])
[
  {
    "_id": "5a0afda01643cf41789e500f",
    "name": "Group1",
    "topic": "This stuff",
    "currentMove": 3,
    "invitee": [
      {
        "_id": "5a0afda01643cf41789e5012",
        "user_id": {
          "_id": "5a0afda01643cf41789e500c",
          "__v": 0,
          "name": "Fred",
          "currentMove": 3
        },
        "confirmed": true
      }
    ],
    "__v": 0
  }
]
Horrible populate example :(
Mongoose: groups.find({}, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("5a0afda01643cf41789e500a"), ObjectId("5a0afda01643cf41789e500b"), ObjectId("5a0afda01643cf41789e500c"), ObjectId("5a0afda01643cf41789e500d"), ObjectId("5a0afda01643cf41789e500e") ] }, '$where': 'this.currentMove === 3' }, { fields: {} })
All members still there
[
  {
    "_id": "5a0afda01643cf41789e500f",
    "name": "Group1",
    "topic": "This stuff",
    "currentMove": 3,
    "__v": 0,
    "invitee": [
      {
        "user_id": null,
        "_id": "5a0afda01643cf41789e5014",
        "confirmed": false
      },
      {
        "user_id": null,
        "_id": "5a0afda01643cf41789e5013",
        "confirmed": false
      },
      {
        "user_id": {
          "_id": "5a0afda01643cf41789e500c",
          "__v": 0,
          "name": "Fred",
          "currentMove": 3
        },
        "_id": "5a0afda01643cf41789e5012",
        "confirmed": true
      },
      {
        "user_id": null,
        "_id": "5a0afda01643cf41789e5011",
        "confirmed": false
      },
      {
        "user_id": null,
        "_id": "5a0afda01643cf41789e5010",
        "confirmed": false
      }
    ]
  }
]
Now they are filtered
[
  {
    "_id": "5a0afda01643cf41789e500f",
    "name": "Group1",
    "topic": "This stuff",
    "currentMove": 3,
    "__v": 0,
    "invitee": [
      {
        "user_id": {
          "_id": "5a0afda01643cf41789e500c",
          "__v": 0,
          "name": "Fred",
          "currentMove": 3
        },
        "_id": "5a0afda01643cf41789e5012",
        "confirmed": true
      }
    ]
  }
]

populate()

, .populate() . , , , , , "join":

   // Note that we cannot populate "here" since we need the returned value
   let results = await Group.find();

   // The value is only in context as we use `Array.map()` to process each result
   results = await Promise.all(
    results.map( r =>
      User.populate(r,{
        path: 'invitee.user_id',
        match: { "$where": `this.currentMove === ${r.currentMove}` }
      })
    )
  );

  console.log("All members still there");
  log(results);

  // Then we clean it for null values

  results = results.map( r =>
    Object.assign(r,{
      invitee: r.invitee.filter(i => i.user_id !== null)
    })
  );

  console.log("Now they are filtered");
  log(results);

, , .

, "" . (, ), . .

, populate() "" , . , , null:

[
  {
    "_id": "5a0afa889f9f7e4064d8794d",
    "name": "Group1",
    "topic": "This stuff",
    "currentMove": 3,
    "__v": 0,
    "invitee": [
      {
        "user_id": null,
        "_id": "5a0afa889f9f7e4064d87952",
        "confirmed": false
      },
      {
        "user_id": null,
        "_id": "5a0afa889f9f7e4064d87951",
        "confirmed": false
      },
      {
        "user_id": {
          "_id": "5a0afa889f9f7e4064d8794a",
          "__v": 0,
          "name": "Fred",
          "currentMove": 3
        },
        "_id": "5a0afa889f9f7e4064d87950",
        "confirmed": true
      },
      {
        "user_id": null,
        "_id": "5a0afa889f9f7e4064d8794f",
        "confirmed": false
      },
      {
        "user_id": null,
        "_id": "5a0afa889f9f7e4064d8794e",
        "confirmed": false
      }
    ]
  }
]

Array.filter(), "", , .

" " -. , . , , Array.filter(), .

, . , ", " , .

+1

Source: https://habr.com/ru/post/1689105/


All Articles