Mongoose バーチャル
Mongooseでは、バーチャルはMongoDBに**保存されない**プロパティです。バーチャルは通常、ドキュメントの計算されたプロパティに使用されます。
初めてのバーチャル
User
モデルがあるとします。すべてのユーザーにはemail
がありますが、メールのドメインも必要です。たとえば、'test@gmail.com'のドメイン部分は 'gmail.com' です。
以下は、バーチャルを使用してdomain
プロパティを実装する1つの方法です。Schema#virtual()
関数を使用して、スキーマにバーチャルを定義します。
const userSchema = mongoose.Schema({
email: String
});
// Create a virtual property `domain` that's computed from `email`.
userSchema.virtual('domain').get(function() {
return this.email.slice(this.email.indexOf('@') + 1);
});
const User = mongoose.model('User', userSchema);
const doc = await User.create({ email: 'test@gmail.com' });
// `domain` is now a property on User documents.
doc.domain; // 'gmail.com'
Schema#virtual()
関数は、VirtualType
オブジェクトを返します。通常のドキュメントプロパティとは異なり、バーチャルには基になる値がなく、Mongooseはバーチャルに対して型強制を行いません。ただし、バーチャルにはゲッターとセッターがあり、上記のdomain
の例のように、計算されたプロパティに最適です。
バーチャルセッター
通常のプロパティのカスタムセッターの代わりに、バーチャルを使用して複数のプロパティを一度に設定することもできます。たとえば、firstName
とlastName
という2つの文字列プロパティがあるとします。これらの両方のプロパティを一度に設定できるバーチャルプロパティfullName
を作成できます。重要な詳細は、バーチャルゲッターとセッターでは、this
はバーチャルがアタッチされているドキュメントを参照することです。
const userSchema = mongoose.Schema({
firstName: String,
lastName: String
});
// Create a virtual property `fullName` with a getter and setter.
userSchema.virtual('fullName').
get(function() { return `${this.firstName} ${this.lastName}`; }).
set(function(v) {
// `v` is the value being set, so use the value to set
// `firstName` and `lastName`.
const firstName = v.substring(0, v.indexOf(' '));
const lastName = v.substring(v.indexOf(' ') + 1);
this.set({ firstName, lastName });
});
const User = mongoose.model('User', userSchema);
const doc = new User();
// Vanilla JavaScript assignment triggers the setter
doc.fullName = 'Jean-Luc Picard';
doc.fullName; // 'Jean-Luc Picard'
doc.firstName; // 'Jean-Luc'
doc.lastName; // 'Picard'
JSONでのバーチャル
デフォルトでは、MongooseはドキュメントをJSONに変換するときにバーチャルを含めません。たとえば、ドキュメントをExpressのres.json()
関数に渡すと、デフォルトではバーチャルは**含まれません**。
res.json()
にバーチャルを含めるには、toJSON
スキーマオプションを{ virtuals: true }
に設定する必要があります。
const opts = { toJSON: { virtuals: true } };
const userSchema = mongoose.Schema({
_id: Number,
email: String
}, opts);
// Create a virtual property `domain` that's computed from `email`.
userSchema.virtual('domain').get(function() {
return this.email.slice(this.email.indexOf('@') + 1);
});
const User = mongoose.model('User', userSchema);
const doc = new User({ _id: 1, email: 'test@gmail.com' });
doc.toJSON().domain; // 'gmail.com'
// {"_id":1,"email":"test@gmail.com","domain":"gmail.com","id":"1"}
JSON.stringify(doc);
// To skip applying virtuals, pass `virtuals: false` to `toJSON()`
doc.toJSON({ virtuals: false }).domain; // undefined
console.log()
でのバーチャル
デフォルトでは、Mongooseはconsole.log()
出力にバーチャルを**含めません**。 console.log()
にバーチャルを含めるには、toObject
スキーマオプションを{ virtuals: true }
に設定するか、オブジェクトを出力する前にtoObject()
を使用する必要があります。
console.log(doc.toObject({ virtuals: true }));
Leanを使ったバーチャル
バーチャルはMongooseドキュメントのプロパティです。leanオプションを使用する場合、クエリは完全なMongooseドキュメントではなくPOJOを返します。つまり、lean()
を使用すると、バーチャルは存在しません。
const fullDoc = await User.findOne();
fullDoc.domain; // 'gmail.com'
const leanDoc = await User.findOne().lean();
leanDoc.domain; // undefined
パフォーマンスのためにlean()
を使用する必要があるが、バーチャルも必要な場合は、Mongooseには、バーチャルでleanドキュメントを装飾する公式にサポートされているmongoose-lean-virtuals
プラグインがあります。
制限事項
MongooseバーチャルはMongoDBに**保存されない**ため、Mongooseバーチャルに基づいてクエリを実行することはできません。
// Will **not** find any results, because `domain` is not stored in
// MongoDB.
const doc = await User.findOne({ domain: 'gmail.com' }, null, { strictQuery: false });
doc; // undefined
計算されたプロパティでクエリを実行する場合は、カスタムセッターまたはプリセーブミドルウェアを使用してプロパティを設定する必要があります。
Populate
MongooseはバーチャルのPopulateもサポートしています。Populateされたバーチャルには、別のコレクションのドキュメントが含まれています。Populateされたバーチャルを定義するには、以下を指定する必要があります。
ref
オプション。ドキュメントをPopulateするモデルをMongooseに指示します。localField
およびforeignField
オプション。Mongooseは、foreignField
がこのドキュメントのlocalField
と一致するref
のモデルからドキュメントをPopulateします。
const userSchema = mongoose.Schema({ _id: Number, email: String });
const blogPostSchema = mongoose.Schema({
title: String,
authorId: Number
});
// When you `populate()` the `author` virtual, Mongoose will find the
// first document in the User model whose `_id` matches this document's
// `authorId` property.
blogPostSchema.virtual('author', {
ref: 'User',
localField: 'authorId',
foreignField: '_id',
justOne: true
});
const User = mongoose.model('User', userSchema);
const BlogPost = mongoose.model('BlogPost', blogPostSchema);
await BlogPost.create({ title: 'Introduction to Mongoose', authorId: 1 });
await User.create({ _id: 1, email: 'test@gmail.com' });
const doc = await BlogPost.findOne().populate('author');
doc.author.email; // 'test@gmail.com'
スキーマオプションによるバーチャル
.virtual
を使用せずに、スキーマオプションでバーチャルを直接定義することもできます。
const userSchema = mongoose.Schema({
firstName: String,
lastName: String
}, {
virtuals: {
// Create a virtual property `fullName` with a getter and setter
fullName: {
get() { return `${this.firstName} ${this.lastName}`; },
set(v) {
// `v` is the value being set, so use the value to set
// `firstName` and `lastName`.
const firstName = v.substring(0, v.indexOf(' '));
const lastName = v.substring(v.indexOf(' ') + 1);
this.set({ firstName, lastName });
}
}
}
});
const User = mongoose.model('User', userSchema);
const doc = new User();
// Vanilla JavaScript assignment triggers the setter
doc.fullName = 'Jean-Luc Picard';
doc.fullName; // 'Jean-Luc Picard'
doc.firstName; // 'Jean-Luc'
doc.lastName; // 'Picard'
バーチャルPopulateなどのバーチャルオプションにも同じことが言えます。
const userSchema = mongoose.Schema({ _id: Number, email: String });
const blogPostSchema = mongoose.Schema({
title: String,
authorId: Number
}, {
virtuals: {
// When you `populate()` the `author` virtual, Mongoose will find the
// first document in the User model whose `_id` matches this document's
// `authorId` property.
author: {
options: {
ref: 'User',
localField: 'authorId',
foreignField: '_id',
justOne: true
}
}
}
});
const User = mongoose.model('User', userSchema);
const BlogPost = mongoose.model('BlogPost', blogPostSchema);
await BlogPost.create({ title: 'Introduction to Mongoose', authorId: 1 });
await User.create({ _id: 1, email: 'test@gmail.com' });
const doc = await BlogPost.findOne().populate('author');
doc.author.email; // 'test@gmail.com'