web-dev-qa-db-ja.com

Mongooseのサブドキュメントの検索/更新

ドキュメントには次のスキーマがありますFolder

var permissionSchema = new Schema({
    role: { type: String },
    create_folders: { type: Boolean },
    create_contents: { type: Boolean }
});

var folderSchema = new Schema({
    name: { type: string },
    permissions: [ permissionSchema ]
});

そのため、各ページには多くの権限を付与できます。 CMSには、すべてのフォルダーとそのアクセス許可を一覧表示するパネルがあります。管理者は、単一の権限を編集して保存できます。

Folderドキュメント全体をそのアクセス許可配列で簡単に保存できましたが、1つのアクセス許可のみが変更されました。しかし、私はすべてのドキュメントを保存したくありません(実際のスキーマにはもっと多くのフィールドがあります)。

savePermission: function (folderId, permission, callback) {
    Folder.findOne({ _id: folderId }, function (err, data) {
        var perm = _.findWhere(data.permissions, { _id: permission._id });                

        _.extend(perm, permission);

        data.markModified("permissions");
        data.save(callback);
    });
}

しかし問題はpermが常にndefined!この方法で許可を「静的に」取得しようとしました:

var perm = data.permissions[0];

そしてそれはうまく機能するので、問題はUnderscoreライブラリが権限配列を照会できないことです。だから私は、フェッチされたドキュメントのサブドキュメントを取得するためのより良い(そして作業中の)方法があると思います。

何か案が?

PS: "for"ループを使用してdata.permissions [i] ._ id == permission._idを使用してdata.permission配列内の各項目をチェックすることを解決しましたが、よりスマートなソリューションが必要です。 one !

42
Darko Romanov

したがって、mongooseのデフォルトでは、このような配列にデータを「埋め込む」と、各配列エントリのサブドキュメントプロパティの一部として__id_値が取得されます。実際に、更新する予定のアイテムのインデックスを決定するために、この値を使用できます。これを行うMongoDBの方法は、 positional _$_ 演算子変数で、配列内の「一致した」位置を保持します。

_Folder.findOneAndUpdate(
    { "_id": folderId, "permissions._id": permission._id },
    { 
        "$set": {
            "permissions.$": permission
        }
    },
    function(err,doc) {

    }
);
_

その .findOneAndUpdate() メソッドは変更されたドキュメントを返すか、ドキュメントが不要な場合はメソッドとして.update()を使用できます戻ってきた。主な部分は、前述のように、更新する配列の要素を「一致」させ、 位置_$_ と一致する「識別」です。

それからもちろん _$set_ 演算子を使用してonly指定した要素は、実際に「有線」でサーバーに送信されます。 "dot notation" でこれをさらに進め、実際に更新したい要素を指定するだけです。次のように:

_Folder.findOneAndUpdate(
    { "_id": folderId, "permissions._id": permission._id },
    { 
        "$set": {
            "permissions.$.role": permission.role
        }
    },
    function(err,doc) {

    }
);
_

したがって、これはMongoDBが提供する柔軟性であり、実際にドキュメントを更新する方法を非常に「ターゲット」にすることができます。

ただし、これが行うことは、「検証」やその他の「事前保存フック」など、「mongoose」スキーマに組み込まれているロジックを「バイパス」することです。これは、「最適な」方法がMongoDBの「機能」であり、その設計方法だからです。 Mongoose自体は、このロジックの「便利な」ラッパーになろうとしています。ただし、自分で制御する準備ができている場合は、最適な方法で更新を行うことができます。

そのため、可能な場合は、データを「埋め込み」のままにして、参照モデルを使用しないでください。並行性について心配する必要のない単純な更新で、「親」と「子」の両方の項目のアトミック更新を許可します。そもそもMongoDBを選択すべき理由の1つでしょう。

93
Neil Lunn

Mongooseで更新する際にサブドキュメントを検証するには、スキーマオブジェクトとして「ロード」する必要があります。そうすると、Mongooseは自動的に検証とフックをトリガーします。

_const userSchema = new mongoose.Schema({
  // ...
  addresses: [addressSchema],
});
_

サブドキュメントの配列がある場合は、Mongooseが提供するid()メソッドを使用して目的のサブドキュメントを取得できます。次に、そのフィールドを個別に更新するか、複数のフィールドを一度に更新する場合は、set()メソッドを使用します。

_User.findById(userId)
  .then((user) => {
    const address = user.addresses.id(addressId); // returns a matching subdocument
    address.set(req.body); // updates the address while keeping its schema       
    // address.zipCode = req.body.zipCode; // individual fields can be set directly

    return user.save(); // saves document with subdocuments and triggers validation
  })
  .then((user) => {
    res.send({ user });
  })
  .catch(e => res.status(400).send(e));
_

ユーザー文書を見つけるのにuserIdは本当に必要ないことに注意してください。次のようにaddressIdに一致する住所サブ文書を持つものを検索することで取得できます。

_User.findOne({
  'addresses._id': addressId,
})
// .then() ... the same as the example above
_

MongoDBでは、親ドキュメントが保存されるときにサブドキュメントが保存されるonlyことを忘れないでください。

公式ドキュメント のトピックに関する詳細をお読みください。

13
Arian Acosta

別のコレクションが必要ない場合は、permissionSchemaをfolderSchemaに埋め込みます。

_var folderSchema = new Schema({
    name: { type: string },
    permissions: [ {
        role: { type: String },
        create_folders: { type: Boolean },
        create_contents: { type: Boolean }
    } ]
});
_

個別のコレクションが必要な場合、これが最善のアプローチです。

許可モデルを持つことができます:

_var mongoose = require('mongoose');
var PermissionSchema = new Schema({
  role: { type: String },
  create_folders: { type: Boolean },
  create_contents: { type: Boolean }
});

module.exports = mongoose.model('Permission', PermissionSchema);
_

そして、許可ドキュメントへの参照を含むフォルダーモデル。次のような別のスキーマを参照できます。

_var mongoose = require('mongoose');
var FolderSchema = new Schema({
  name: { type: string },
  permissions: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Permission' } ]
});

module.exports = mongoose.model('Folder', FolderSchema);
_

次に、Folder.findOne().populate('permissions')を呼び出して、mongooseにフィールドのアクセス許可を設定するように依頼します。

さて、次は:

_savePermission: function (folderId, permission, callback) {
    Folder.findOne({ _id: folderId }).populate('permissions').exec(function (err, data) {
        var perm = _.findWhere(data.permissions, { _id: permission._id });                

        _.extend(perm, permission);

        data.markModified("permissions");
        data.save(callback);
    });
}
_

permフィールドは、Mongooseによって設定されているため、permission._idが実際にpermissions配列にある場合、未定義になりません。

6
RaphDG