ドキュメント

Mongoose ドキュメントは、MongoDBに保存されているドキュメントへの1対1のマッピングを表します。各ドキュメントは、そのモデルのインスタンスです。

ドキュメントとモデルの違い

DocumentModel は、Mongoose において異なるクラスです。Model クラスは Document クラスのサブクラスです。Model コンストラクタを使用すると、新しいドキュメントが作成されます。

const MyModel = mongoose.model('Test', new Schema({ name: String }));
const doc = new MyModel();

doc instanceof MyModel; // true
doc instanceof mongoose.Model; // true
doc instanceof mongoose.Document; // true

Mongoose では、一般的に「ドキュメント」とはモデルのインスタンスを意味します。モデルを経由せずに Document クラスのインスタンスを作成する必要はありません。

取得

findOne() などのモデル関数を使用して MongoDB からドキュメントをロードすると、Mongoose ドキュメントが返されます。

const doc = await MyModel.findOne();

doc instanceof MyModel; // true
doc instanceof mongoose.Model; // true
doc instanceof mongoose.Document; // true

save() を使用した更新

Mongoose ドキュメントは変更を追跡します。通常の JavaScript の代入を使用してドキュメントを変更すると、Mongoose はそれをMongoDB 更新演算子に変換します。

doc.name = 'foo';

// Mongoose sends an `updateOne({ _id: doc._id }, { $set: { name: 'foo' } })`
// to MongoDB.
await doc.save();

save() メソッドは Promise を返します。save() が成功すると、Promise は保存されたドキュメントに解決されます。

doc.save().then(savedDoc => {
  savedDoc === doc; // true
});

対応する _id を持つドキュメントが見つからない場合、Mongoose は DocumentNotFoundError を報告します。

const doc = await MyModel.findOne();

// Delete the document so Mongoose won't be able to save changes
await MyModel.deleteOne({ _id: doc._id });

doc.name = 'foo';
await doc.save(); // Throws DocumentNotFoundError

ネストされたプロパティの設定

Mongoose ドキュメントには、深くネストされたプロパティを安全に設定するために使用できる set() 関数があります。

const schema = new Schema({
  nested: {
    subdoc: new Schema({
      name: String
    })
  }
});
const TestModel = mongoose.model('Test', schema);

const doc = new TestModel();
doc.set('nested.subdoc.name', 'John Smith');
doc.nested.subdoc.name; // 'John Smith'

Mongoose ドキュメントには、深くネストされたプロパティを安全に読み取るための get() 関数もあります。get() を使用すると、JavaScript のオプショナルチェイニング演算子 ?. と同様に、nullish 値を明示的にチェックする必要がなくなります。

const doc2 = new TestModel();

doc2.get('nested.subdoc.name'); // undefined
doc2.nested?.subdoc?.name; // undefined

doc2.set('nested.subdoc.name', 'Will Smith');
doc2.get('nested.subdoc.name'); // 'Will Smith'

Mongoose ドキュメントでは、オプショナルチェイニング ?. および nullish 合体 ?? を使用できます。ただし、nullish 合体代入 ??= を使用して Mongoose ドキュメントでネストされたパスを作成する場合は注意が必要です。

// The following works fine
const doc3 = new TestModel();
doc3.nested.subdoc ??= {};
doc3.nested.subdoc.name = 'John Smythe';

// The following does **NOT** work.
// Do not use the following pattern with Mongoose documents.
const doc4 = new TestModel();
(doc4.nested.subdoc ??= {}).name = 'Charlie Smith';
doc.nested.subdoc; // Empty object
doc.nested.subdoc.name; // undefined.

クエリを使用した更新

save() 関数は、一般的に Mongoose でドキュメントを更新するための適切な方法です。save() を使用すると、完全なバリデーションミドルウェアが適用されます。

save() では柔軟性が足りない場合のために、Mongoose ではキャスト、ミドルウェア、および制限付きバリデーションを備えた独自のMongoDB 更新を作成できます。

// Update all documents in the `mymodels` collection
await MyModel.updateMany({}, { $set: { name: 'foo' } });

update(), updateMany(), findOneAndUpdate() などは、save() ミドルウェアを実行しないことに注意してください。保存ミドルウェアと完全なバリデーションが必要な場合は、まずドキュメントをクエリしてから save() します。

バリデーション

ドキュメントは保存される前にキャストおよびバリデーションされます。Mongoose は最初に値を指定された型にキャストし、次にバリデーションします。内部的には、Mongoose は保存前にドキュメントのvalidate() メソッドを呼び出します。

const schema = new Schema({ name: String, age: { type: Number, min: 0 } });
const Person = mongoose.model('Person', schema);

const p = new Person({ name: 'foo', age: 'bar' });
// Cast to Number failed for value "bar" at path "age"
await p.validate();

const p2 = new Person({ name: 'foo', age: -1 });
// Path `age` (-1) is less than minimum allowed value (0).
await p2.validate();

Mongoose は、runValidators オプションを使用した更新時の制限付きバリデーションもサポートしています。Mongoose は、デフォルトで findOne()updateOne() などのクエリ関数のパラメータをキャストします。ただし、Mongoose はデフォルトではクエリ関数のパラメータに対してバリデーションを実行しません。Mongoose がバリデーションを実行するには、runValidators: true を設定する必要があります。

// Cast to number failed for value "bar" at path "age"
await Person.updateOne({}, { age: 'bar' });

// Path `age` (-1) is less than minimum allowed value (0).
await Person.updateOne({}, { age: -1 }, { runValidators: true });

詳細については、バリデーションガイドをお読みください。

上書き

ドキュメントを上書き(ドキュメント内のすべてのキーを置き換える)する方法は2つあります。1つは、Document#overwrite() 関数の後に save() を使用する方法です。

const doc = await Person.findOne({ _id });

// Sets `name` and unsets all other properties
doc.overwrite({ name: 'Jean-Luc Picard' });
await doc.save();

もう1つの方法は、Model.replaceOne() を使用する方法です。

// Sets `name` and unsets all other properties
await Person.replaceOne({ _id }, { name: 'Jean-Luc Picard' });

次へ

ドキュメントについて説明したので、次はサブドキュメントを見てみましょう。