Заполнить вложенный массив в мангусте

111

Как я могу заполнить «компоненты» в примере документа:

  {
    "__v": 1,
    "_id": "5252875356f64d6d28000001",
    "pages": [
      {
        "__v": 1,
        "_id": "5252875a56f64d6d28000002",
        "page": {
          "components": [
            "525287a01877a68528000001"
          ]
        }
      }
    ],
    "author": "Book Author",
    "title": "Book Title"
  }

Это мой JS, где я получаю документ от Mongoose:

  Project.findById(id).populate('pages').exec(function(err, project) {
    res.json(project);
  });
Антон Шувалов
источник
Сейчас он пуст? Какие результаты вы получаете?
WiredPrairie
2
если я напишу, ...populate('pages pages.page.components').exec...я получу то же, что указано в примере документа. Ничего не изменилось.
Антон Шувалов

Ответы:

251

Mongoose 4.5 поддерживает это

Project.find(query)
  .populate({ 
     path: 'pages',
     populate: {
       path: 'components',
       model: 'Component'
     } 
  })
  .exec(function(err, docs) {});

И вы можете присоединиться к более чем одному глубокому уровню

Трин Хоанг Нху
источник
14
Удивительно - настолько чище! Это современный и правильный ответ. Документировано здесь .
isTravis
@NgaNguyenDuy github.com/Automattic/mongoose/wiki/4.0-Release-Notes сказал, что эта функция уже существует с 4.0. У вас может быть неправильный запрос.
Trinh Hoang Nhu,
1
@TrinhHoangNhu Я не делал примечания к выпуску 4.0, но меня попробовали. Мой запрос ничего не возвращает, если я запускаю его как mongoose 4.0, но он работал нормально при обновлении до версии 4.5.8. Мой запрос: gist.github.com/NgaNguyenDuy/998f7714fb768427abf5838fafa573d7
NgaNguyenDuy
1
@NgaNguyenDuy Мне также нужно было обновить до 4.5.8, чтобы это работало !!
vinesh
4
Я не понимаю, как это будет работать, поскольку пути pages.$.page.componentнет pages.$.component. Как он узнает, что нужно искать в объекте страницы?
Доминик
111

Это подходит для меня:

 Project.find(query)
  .lean()
  .populate({ path: 'pages' })
  .exec(function(err, docs) {

    var options = {
      path: 'pages.components',
      model: 'Component'
    };

    if (err) return res.json(500);
    Project.populate(docs, options, function (err, projects) {
      res.json(projects);
    });
  });

Документация: Model.populate

Антон Шувалов
источник
9
«Модель: Компонент» действительно важно сохранить!
Totty.js
3
Но не должно, потому что, когда я определяю ссылку, я также определяю модель, это не совсем СУХОЙ. В любом случае, спасибо, работает;)
Totty.js
Будьте осторожны с бережливым методом. Вы не сможете вызывать пользовательские методы или даже экономить на возвращаемых объектах.
Даниэль Кмак
Lean () в моем случае не нужен, но остальное работает прекрасно.
Джон
1
Возможно ли заполнить еще один «уровень» глубже?
timhc22
35

Как отмечали другие, Mongoose 4поддерживает это. Очень важно отметить, что при необходимости вы также можете выполнять рекурсию глубже одного уровня, хотя в документации это не указано:

Project.findOne({name: req.query.name})
    .populate({
        path: 'threads',
        populate: {
            path: 'messages', 
            model: 'Message',
            populate: {
                path: 'user',
                model: 'User'
            }
        }
    })
Никк Вонг
источник
28

Таким образом вы можете заполнить несколько вложенных документов.

   Project.find(query)
    .populate({ 
      path: 'pages',
      populate: [{
       path: 'components',
       model: 'Component'
      },{
        path: 'AnotherRef',
        model: 'AnotherRef',
        select: 'firstname lastname'
      }] 
   })
   .exec(function(err, docs) {});
Шауль Хамид
источник
1
заполнение путей в массиве также сработало для меня:populate: ['components','AnotherRef']
Ясин Окумуш
Для меня в версии 5.5.7 обозначение массива, о котором упоминал Ясин, не работало, вместо этого работает связь в одной строке. т.е.populate: 'components AnotherRef'
Samih A
8

Лучшее решение:

Car
 .find()
 .populate({
   path: 'pages.page.components'
})
Tuấn Anh ào
источник
Все остальные ответы излишне сложны, это должно быть общепринятым решением.
SeedyROM
И это решает случай, когда pageесть другие ненаселенные свойства.
Сира Лам
4

Я нашел это очень полезным, создав перышки перед хуком для заполнения двухуровневого отношения. У моделей мангуста просто есть

tables = new Schema({
  ..
  tableTypesB: { type: Schema.Types.ObjectId, ref: 'tableTypesB' },
  ..
}
tableTypesB = new Schema({
  ..
  tableType: { type: Schema.Types.ObjectId, ref: 'tableTypes' },
  ..
}

затем в перьях перед крючком:

module.exports = function(options = {}) {
  return function populateTables(hook) {
    hook.params.query.$populate = {
      path: 'tableTypesB',
      populate: { path: 'tableType' }
    }

    return Promise.resolve(hook)
  }
}

Так просто по сравнению с некоторыми другими методами, которые я пытался добиться.

Трэвис С
источник
Если вы не беспокоитесь о перезаписи запроса $ populate, который мог быть передан. В этом случае вы должны использовать hook.params.query. $ Populate = Object.assign (hook.params.query. $ Populate || {}, {/ * здесь новый объект заполнения * /})
Travis S
1

Я нашел этот вопрос через другой вопрос, который касался KeystoneJS, но был отмечен как повторяющийся. Если кто-то здесь может искать ответ Keystone, вот как я выполнил свой запрос с глубоким заполнением в Keystone.

Двухуровневая популяция мангуста с использованием KeystoneJs [дубликат]

exports.getStoreWithId = function (req, res) {
    Store.model
        .find()
        .populate({
            path: 'productTags productCategories',
            populate: {
                path: 'tags',
            },
        })
        .where('updateId', req.params.id)
        .exec(function (err, item) {
            if (err) return res.apiError('database error', err);
            // possibly more than one
            res.apiResponse({
                store: item,
            });
        });
};
Леопольд Кристьянссон
источник
1

Вы также можете сделать это, используя $lookupагрегацию, и, вероятно, лучший способ, так как теперь заселение вымирает из монго

Project.aggregate([
  { "$match": { "_id": mongoose.Types.ObjectId(id) } },
  { "$lookup": {
    "from": Pages.collection.name,
    "let": { "pages": "$pages" },
    "pipeline": [
      { "$match": { "$expr": { "$in": [ "$_id", "$$pages" ] } } },
      { "$lookup": {
        "from": Component.collection.name,
        "let": { "components": "$components" },
        "pipeline": [
          { "$match": { "$expr": { "$in": [ "$_id", "$$components" ] } } },
        ],
        "as": "components"
      }},
    ],
    "as": "pages"
  }}
])
Ashh
источник
1

Mongoose 5.4 поддерживает это

Project.find(query)
.populate({
  path: 'pages.page.components',
  model: 'Component'
})
Надун Лиянаге
источник
0

Для тех, у кого есть проблема, populateи они тоже хотят это сделать:

  • чат с простым текстом и быстрыми ответами (пузыри)
  • 4 коллекции базы данных для чата: clients, users, rooms, messasges.
  • одинаковая структура БД сообщений для 3 типов отправителей: бот, пользователи и клиенты
  • refPathили динамическая ссылка
  • populateс pathиmodel варианты
  • использовать findOneAndReplace/ replaceOneс$exists
  • создать новый документ, если полученный документ не существует

КОНТЕКСТ

Цель

  1. Сохраните новое простое текстовое сообщение в базе данных и заполните его данными пользователя или клиента (2 разные модели).
  2. Сохраните новое сообщение quickReplies в базе данных и заполните его данными пользователя или клиента.
  3. Сохранить каждое сообщение его тип отправителя: clients, users&bot .
  4. Заполняйте только сообщения, у которых есть отправитель clientsили usersего модели Mongoose. Клиентские модели типа _sender есть clients, для пользователя есть users.

Схема сообщения :

const messageSchema = new Schema({
    room: {
        type: Schema.Types.ObjectId,
        ref: 'rooms',
        required: [true, `Room's id`]
    },
    sender: {
         _id: { type: Schema.Types.Mixed },
        type: {
            type: String,
            enum: ['clients', 'users', 'bot'],
            required: [true, 'Only 3 options: clients, users or bot.']
        }
    },
    timetoken: {
        type: String,
        required: [true, 'It has to be a Nanosecond-precision UTC string']
    },
    data: {
        lang: String,
        // Format samples on https://docs.chatfuel.com/api/json-api/json-api
        type: {
            text: String,
            quickReplies: [
                {
                    text: String,
                    // Blocks' ids.
                    goToBlocks: [String]
                }
            ]
        }
    }

mongoose.model('messages', messageSchema);

РЕШЕНИЕ

Мой запрос API на стороне сервера

Мой код

Служебная функция (в chatUtils.jsфайле) для получения сообщения типа, которое вы хотите сохранить:

/**
 * We filter what type of message is.
 *
 * @param {Object} message
 * @returns {string} The type of message.
 */
const getMessageType = message => {
    const { type } = message.data;
    const text = 'text',
        quickReplies = 'quickReplies';

    if (type.hasOwnProperty(text)) return text;
    else if (type.hasOwnProperty(quickReplies)) return quickReplies;
};

/**
 * Get the Mongoose's Model of the message's sender. We use
 * the sender type to find the Model.
 *
 * @param {Object} message - The message contains the sender type.
 */
const getSenderModel = message => {
    switch (message.sender.type) {
        case 'clients':
            return 'clients';
        case 'users':
            return 'users';
        default:
            return null;
    }
};

module.exports = {
    getMessageType,
    getSenderModel
};

Моя серверная часть (с использованием Nodejs), чтобы получить запрос на сохранение сообщения:

app.post('/api/rooms/:roomId/messages/new', async (req, res) => {
        const { roomId } = req.params;
        const { sender, timetoken, data } = req.body;
        const { uuid, state } = sender;
        const { type } = state;
        const { lang } = data;

        // For more info about message structure, look up Message Schema.
        let message = {
            room: new ObjectId(roomId),
            sender: {
                _id: type === 'bot' ? null : new ObjectId(uuid),
                type
            },
            timetoken,
            data: {
                lang,
                type: {}
            }
        };

        // ==========================================
        //          CONVERT THE MESSAGE
        // ==========================================
        // Convert the request to be able to save on the database.
        switch (getMessageType(req.body)) {
            case 'text':
                message.data.type.text = data.type.text;
                break;
            case 'quickReplies':
                // Save every quick reply from quickReplies[].
                message.data.type.quickReplies = _.map(
                    data.type.quickReplies,
                    quickReply => {
                        const { text, goToBlocks } = quickReply;

                        return {
                            text,
                            goToBlocks
                        };
                    }
                );
                break;
            default:
                break;
        }

        // ==========================================
        //           SAVE THE MESSAGE
        // ==========================================
        /**
         * We save the message on 2 ways:
         * - we replace the message type `quickReplies` (if it already exists on database) with the new one.
         * - else, we save the new message.
         */
        try {
            const options = {
                // If the quickRepy message is found, we replace the whole document.
                overwrite: true,
                // If the quickRepy message isn't found, we create it.
                upsert: true,
                // Update validators validate the update operation against the model's schema.
                runValidators: true,
                // Return the document already updated.
                new: true
            };

            Message.findOneAndUpdate(
                { room: roomId, 'data.type.quickReplies': { $exists: true } },
                message,
                options,
                async (err, newMessage) => {
                    if (err) {
                        throw Error(err);
                    }

                    // Populate the new message already saved on the database.
                    Message.populate(
                        newMessage,
                        {
                            path: 'sender._id',
                            model: getSenderModel(newMessage)
                        },
                        (err, populatedMessage) => {
                            if (err) {
                                throw Error(err);
                            }

                            res.send(populatedMessage);
                        }
                    );
                }
            );
        } catch (err) {
            logger.error(
                `#API Error on saving a new message on the database of roomId=${roomId}. ${err}`,
                { message: req.body }
            );

            // Bad Request
            res.status(400).send(false);
        }
    });

СОВЕТЫ :

Для базы данных:

  • Каждое сообщение - это сам документ.
  • Вместо использования refPathмы используем утилиту, getSenderModelкоторая используется в populate(). Это из-за бота. sender.typeМожет быть: usersс его базой данных, clientsс его базой данных и botбез базы данных. refPathНуждается в истинной эталонной модели, если нет, то Mongooose выдаст ошибку.
  • sender._idможет быть типа ObjectIdдля пользователей и клиентов или nullдля бота.

Для логики запроса API:

  • Заменим quickReplyсообщение (Message DB должен иметь только один quickReply, но , как многие простые текстовые сообщения , как вы хотите). Мы используем findOneAndUpdateвместо replaceOneили findOneAndReplace.
  • Мы выполняем операцию запроса ( findOneAndUpdate) и populateоперацию с callbackкаждым из них. Это важно , если вы не знаете , если использовать async/await, then(), exec()или callback(err, document). Для получения дополнительной информации см. « Заполнить документ» .
  • Мы заменяем сообщение быстрого ответа overwriteопцией и без $setоператора запроса.
  • Если мы не найдем быстрого ответа, мы создадим новый. Вы должны сообщить об этом Mongoose с помощью upsertoption.
  • Мы заполняем только один раз, для замененного сообщения или нового сохраненного сообщения.
  • Мы возвращаемся к обратным вызовам независимо от того, какое сообщение мы сохранили с findOneAndUpdateи для populate().
  • В populate, мы создаем настраиваемую ссылку на динамическую модель с расширением getSenderModel. Мы можем использовать динамическую ссылку Mongoose, потому что sender.typefor botне имеет модели Mongoose. Мы используем базу данных для заполнения с помощью modelи pathoptins.

Я провел много часов, решая небольшие проблемы здесь и там, и я надеюсь, что это кому-то поможет! 😃

Guillem
источник
0

Я боролась с этим целый кровавый день. Ни одно из вышеперечисленных решений не помогло. Единственное, что сработало в моем случае для следующего примера:

{
  outerProp1: {
    nestedProp1: [
      { prop1: x, prop2: y, prop3: ObjectId("....")},
      ...
    ],
    nestedProp2: [
      { prop1: x, prop2: y, prop3: ObjectId("....")},
      ...
    ]
  },
  ...
}

заключается в следующем: (Предполагается, что заполнение после выборки, но также работает при вызове заполнения из класса модели (за которым следует exec))

await doc.populate({
  path: 'outerProp1.nestedProp1.prop3'
}).execPopulate()

// doc is now populated

Другими словами, свойство самого внешнего пути должно содержать полный путь. Похоже, что ни один частично полный путь в сочетании с заполнением свойств не работал (и свойство модели не кажется необходимым; имеет смысл, поскольку оно включено в схему). У меня ушел целый день, чтобы понять это! Не уверен, почему другие примеры не работают.

(Используя Mongoose 5.5.32)

Samuel G
источник
-3

Удалить ссылку на документы

if (err) {
    return res.json(500);
}
Project.populate(docs, options, function (err, projects) {
    res.json(projects);
});

Это сработало для меня.

if (err) {
    return res.json(500);
}
Project.populate(options, function (err, projects) {
    res.json(projects);
});
user4717265
источник