web-dev-qa-db-fra.com

Remplir un tableau imbriqué dans la mangouste

Comment puis-je renseigner "composants" dans l'exemple de document:

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

C'est mon JS où je reçois un document de Mongoose:

  Project.findById(id).populate('pages').exec(function(err, project) {
    res.json(project);
  });
93
Anton Shuvalov

Mongoose 4.5 supporte cette

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

Ça marche pour moi:

 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);
    });
  });

Documentation: Model.populate

104
Anton Shuvalov

Comme d'autres l'ont noté, Mongoose 4 supporte cela. Il est très important de noter que vous pouvez également accéder à plus d'un niveau, si cela est nécessaire - bien que cela ne soit pas noté dans la documentation:

Project.findOne({name: req.query.name})
    .populate({
        path: 'threads',
        populate: {
            path: 'messages', 
            model: 'Message',
            populate: {
                path: 'user',
                model: 'User'
            }
        }
    })
29
nikk wong

Vous pouvez remplir plusieurs documents imbriqués comme ceci.

   Project.find(query)
    .populate({ 
      path: 'pages',
      populate: [{
       path: 'components',
       model: 'Component'
      },{
        path: 'AnotherRef',
        model: 'AnotherRef',
        select: 'firstname lastname'
      }] 
   })
   .exec(function(err, docs) {});
19
Shaul Hameed

J'ai trouvé cela très utile en créant une plume avant le crochet pour peupler une relation profonde à 2 niveaux de référence. Les modèles de mangouste ont simplement

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

puis en plumes avant crochet

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

    return Promise.resolve(hook)
  }
}

Si simple comparé à d'autres méthodes, j'essayais d'y parvenir.

3
Travis S

J'ai trouvé cette question dans une autre question, spécifique à KeystoneJS, mais marquée comme étant en double. Si quelqu'un ici peut être à la recherche d'une réponse Keystone, voici comment j'ai traité ma requête de remplissage en profondeur dans Keystone.

population de Mongoose à deux niveaux utilisant KeystoneJs [duplicate]

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

Vous pouvez le faire en utilisant $lookup agrégation également et probablement la meilleure façon de peupler est de disparaître du mongo

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"
  }}
])
1
Ashh

C'est la meilleure solution:

Car
 .find()
 .populate({
   path: 'pages.page.components'
})
0
Tuấn Anh Đào

Pour quelqu'un qui a le problème avec populate et qui veut aussi le faire:

  • discuter avec un texte simple et des réponses rapides (bulles)
  • 4 collections de bases de données pour le chat: clients, users, rooms, messasges.
  • structure de base de données de même message pour 3 types d’expéditeurs: bot, utilisateurs et clients
  • refPath ou référence dynamique
  • populate avec les options path et model
  • utiliser findOneAndReplace/replaceOne avec $exists
  • créer un nouveau document si le document récupéré n'existe pas

[~ # ~] contexte [~ # ~]

But

  1. Enregistrez un nouveau message texte simple dans la base de données et complétez-le avec les données de l'utilisateur ou du client (2 modèles différents).
  2. Enregistrez un nouveau message quickReplies dans la base de données et remplissez-le avec les données de l'utilisateur ou du client.
  3. Enregistrer chaque message de son type d'expéditeur: clients, users & bot.
  4. Remplissez uniquement les messages dont l'expéditeur est clients ou users avec ses modèles Mongoose. Les modèles client de type _sender sont clients, pour l'utilisateur, users.

Schéma du message :

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);

[~ # ~] solution [~ # ~]

Demande d'API côté serveur

Mon code

Fonction utilitaire (sur le fichier chatUtils.js) Pour obtenir le type de message que vous souhaitez enregistrer:

/**
 * 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
};

Mon côté serveur (utilisant Nodejs) pour obtenir la demande de sauvegarde du message:

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);
        }
    });

CONSEILS :

Pour la base de données:

  • Chaque message est un document lui-même.
  • Au lieu d'utiliser refPath, nous utilisons le util getSenderModel qui est utilisé sur populate(). C'est à cause du bot. Le sender.type Peut être: users avec sa base de données, clients avec sa base de données et bot sans base de données. Le refPath a besoin de vrai Référence de modèle, sinon Mongooose renvoie une erreur.
  • sender._id Peut être de type ObjectId pour les utilisateurs et les clients, ou null pour le bot.

Pour la logique de demande d'API:

  • Nous remplaçons le message quickReply (la base de données de messages ne doit comporter qu’une seule réponse rapide, mais autant de messages texte simples que vous le souhaitez). Nous utilisons le findOneAndUpdate au lieu de replaceOne ou findOneAndReplace.
  • Nous exécutons l'opération de requête (le findOneAndUpdate) et l'opération populate avec le callback de chacun. Ceci est important si vous ne savez pas si utiliser async/await, then(), exec() ou callback(err, document). Pour plus d'informations, regardez le Populate Doc .
  • Nous remplaçons le message de réponse rapide par l'option overwrite et sans opérateur de requête $set.
  • Si nous ne trouvons pas la réponse rapide, nous en créons une nouvelle. Vous devez dire cela à Mongoose avec l'option upsert.
  • Nous remplissons une seule fois, pour le message remplacé ou le nouveau message enregistré.
  • Nous revenons aux rappels, quel que soit le message que nous avons enregistré avec findOneAndUpdate et pour la populate().
  • Dans populate, nous créons une référence de modèle dynamique personnalisée avec le getSenderModel. Nous pouvons utiliser la référence dynamique Mongoose car le sender.type Pour bot n'a pas de modèle Mongoose. Nous utilisons un Populating Across Database avec model et path optins.

J'ai passé beaucoup d'heures à résoudre de petits problèmes ici et là et j'espère que cela aidera quelqu'un! ????

0
Guillem