マングースの入れ子になった配列を作成する


111

サンプルドキュメントに「コンポーネント」を挿入するにはどうすればよいですか。

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

これは、Mongooseがドキュメントを取得する私のJSです。

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

今は空ですか?どんな結果が出ていますか?
WiredPrairie 2013年

2
私が書いた場合...populate('pages pages.page.components').exec...、例のドキュメントに記載されているものと同じものを取得します。何も変更されません。
Anton Shuvalov 2013年

回答:


251

Mongoose 4.5はこれをサポートしています

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

そして、あなたは複数の深いレベルに参加することができます


14
すごい-とてもきれい!これが現在の正しい答えです。 ここに文書化されています
isTravis 2016年

@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 2014

3
しかし、refを定義するときにモデルも定義するため、これは実際にはDRYではないため、そうすべきではありません。とにかく、ありがとう、それは動作します;)
Totty.js '25

リーン方式には注意してください。カスタムメソッドを呼び出したり、返されたオブジェクトを保存したりすることはできません。
Daniel Kmak 14

私の場合はlean()は必要ありませんが、残りは美しく動作します。
john

1
別の「レベル」をより深く設定することは可能ですか?
timhc22

35

他の人が指摘したように、Mongoose 4はこれをサポートしています。必要に応じて、1レベルよりも再帰的に再帰できることに注意することが非常に重要です。ただし、ドキュメントには記載されていません。

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の私にとっては、Yasinが述べた配列表記は機能せず、代わりに1つの文字列での接続が機能しました。iepopulate: 'components AnotherRef'
サミアA

8

それが最善の解決策です。

Car
 .find()
 .populate({
   path: 'pages.page.components'
})

他のすべての答えは不必要に複雑です、これは受け入れられる解決策であるべきです。
SeedyROM、

そして、これにより、page他の入力できないプロパティがあるケースが解決されます。
シララム

4

これは、2 refレベルの深い関係を設定するためにフックする前にfeathersjsを作成するのに非常に役立ちました。マングースモデルは単純に

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

次に、フックの前にfeathersjsで:

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を使用したマングースの2レベルの母集団[重複]

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集計を使用してこれを行うこともできます。現在のところ、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"
  }}
])


0

問題がpopulateあり、これも実行したい人のために:

  • シンプルなテキストとクイック返信(バブル)でチャット
  • チャットのための4つのデータベースのコレクション:clientsusersroomsmessasges
  • ボット、ユーザー、クライアントの3種類の送信者に同じメッセージDB構造
  • refPathまたは動的参照
  • populatepathmodelオプション
  • 使用findOneAndReplace/ replaceOneあり$exists
  • 取得したドキュメントが存在しない場合は、新しいドキュメントを作成します

環境

ゴール

  1. 新しいシンプルなテキストメッセージをデータベースに保存し、ユーザーまたはクライアントデータ(2つの異なるモデル)を入力します。
  2. 新しいquickRepliesメッセージをデータベースに保存し、ユーザーまたはクライアントデータを入力します。
  3. 各メッセージの送信者タイプを保存します:clientsusersbot
  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._idObjectIdユーザーとクライアントのタイプ、またはnullボットのタイプを指定できます。

APIリクエストロジックの場合:

  • quickReplyメッセージを置き換えます(メッセージDBには、quickReplyが1つだけ必要ですが、単純なテキストメッセージはいくつでも必要です)。またはのfindOneAndUpdate代わりに使用します。replaceOnefindOneAndReplace
  • クエリ操作(the findOneAndUpdate)とそれぞれのをpopulate使った操作を実行しますcallback。これは、使用している場合は、あなたがわからない場合に重要であるasync/awaitthen()exec()またはcallback(err, document)。詳細については、Populate Docをご覧ください。
  • overwriteオプションと$setクエリ演算子なしのクイック返信メッセージを置き換えます。
  • クイック返信が見つからない場合は、新しい返信を作成します。upsertオプションでこれをMongooseに伝える必要があります。
  • 置き換えられたメッセージまたは新しく保存されたメッセージに対して、1回のみ入力します。
  • 私たちは、私たちがして保存したメッセージが何であれ、コールバックに戻るfindOneAndUpdateとためpopulate()
  • ではpopulate、を使用してカスタム動的モデル参照を作成しますgetSenderModelsender.typeforにbotはMongooseモデルがないため、Mongoose動的参照を使用できます。私たちは、optins を使用したPopulation Across Databaseを使用しmodelていpathます。

私はあちこちで小さな問題を解決するのに何時間も費やしてきました。これが誰かを助けることを願っています!😃


0

私はこれを血まみれの一日の間苦労しました。上記の解決策はどれもうまくいきませんでした。次のような例で私の場合に機能した唯一のもの:

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

次のことを行うことです:(フェッチ後にデータを取り込むと仮定しますが、ModelクラスからPopulateを呼び出すときにも機能します(続いてexec))

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

// doc is now populated

つまり、最も外側のパスのプロパティには完全なパスを含める必要があります。部分的に完全なパスとプロパティの取り込みの組み合わせは機能していないようです(モデルのプロパティは必要ないようです。スキーマに含まれているため、理にかなっています)。これを理解するのに丸一日かかった!他の例が機能しない理由がわかりません。

(Mongoose 5.5.32を使用)


-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);
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.