鉴别器
¥Discriminators
model.discriminator()
函数
¥The model.discriminator()
function
鉴别器是一种结构继承机制。它们使你能够在同一底层 MongoDB 集合之上拥有具有重叠结构的多个模型。
¥Discriminators are a schema inheritance mechanism. They enable you to have multiple models with overlapping schemas on top of the same underlying MongoDB collection.
假设你想在单个集合中跟踪不同类型的事件。每个事件都会有一个时间戳,但代表点击链接的事件应该有一个 URL。你可以使用 model.discriminator()
函数来实现此目的。该函数采用 3 个参数,一个模型名称、一个鉴别器结构和一个可选键(默认为模型名称)。它返回一个模型,其结构是基本结构和鉴别器结构的并集。
¥Suppose you wanted to track different types of events in a single
collection. Every event will have a timestamp, but events that
represent clicked links should have a URL. You can achieve this
using the model.discriminator()
function. This function takes
3 parameters, a model name, a discriminator schema and an optional
key (defaults to the model name). It returns a model whose schema
is the union of the base schema and the discriminator schema.
const options = { discriminatorKey: 'kind' };
const eventSchema = new mongoose.Schema({ time: Date }, options);
const Event = mongoose.model('Event', eventSchema);
// ClickedLinkEvent is a special type of Event that has
// a URL.
const ClickedLinkEvent = Event.discriminator('ClickedLink',
new mongoose.Schema({ url: String }, options));
// When you create a generic event, it can't have a URL field...
const genericEvent = new Event({ time: Date.now(), url: 'google.com' });
assert.ok(!genericEvent.url);
// But a ClickedLinkEvent can
const clickedEvent = new ClickedLinkEvent({ time: Date.now(), url: 'google.com' });
assert.ok(clickedEvent.url);
鉴别器保存到事件模型的集合中
¥Discriminators save to the Event model's collection
假设你创建了另一个鉴别器来跟踪新用户注册的事件。这些 SignedUpEvent
实例将与通用事件和 ClickedLinkEvent
实例存储在同一集合中。
¥Suppose you created another discriminator to track events where
a new user registered. These SignedUpEvent
instances will be
stored in the same collection as generic events and ClickedLinkEvent
instances.
const event1 = new Event({ time: Date.now() });
const event2 = new ClickedLinkEvent({ time: Date.now(), url: 'google.com' });
const event3 = new SignedUpEvent({ time: Date.now(), user: 'testuser' });
await Promise.all([event1.save(), event2.save(), event3.save()]);
const count = await Event.countDocuments();
assert.equal(count, 3);
鉴别键
¥Discriminator keys
Mongoose 通过 '鉴别键' 来区分不同判别器模型之间的差异,默认情况下为 __t
。Mongoose 将一个名为 __t
的字符串路径添加到你的结构中,用于跟踪该文档是哪个鉴别器的实例。
¥The way Mongoose tells the difference between the different discriminator models is by the 'discriminator key', which is __t
by default.
Mongoose adds a String path called __t
to your schemas that it uses to track which discriminator this document is an instance of.
const event1 = new Event({ time: Date.now() });
const event2 = new ClickedLinkEvent({ time: Date.now(), url: 'google.com' });
const event3 = new SignedUpEvent({ time: Date.now(), user: 'testuser' });
assert.ok(!event1.__t);
assert.equal(event2.__t, 'ClickedLink');
assert.equal(event3.__t, 'SignedUp');
更新鉴别器密钥
¥Updating the discriminator key
默认情况下,Mongoose 不允许你更新鉴别器密钥。如果你尝试更新鉴别器密钥,save()
将引发错误。findOneAndUpdate()
、updateOne()
等将删除鉴别器密钥更新。
¥By default, Mongoose doesn't let you update the discriminator key.
save()
will throw an error if you attempt to update the discriminator key.
And findOneAndUpdate()
, updateOne()
, etc. will strip out discriminator key updates.
let event = new ClickedLinkEvent({ time: Date.now(), url: 'google.com' });
await event.save();
event.__t = 'SignedUp';
// ValidationError: ClickedLink validation failed: __t: Cast to String failed for value "SignedUp" (type string) at path "__t"
await event.save();
event = await ClickedLinkEvent.findByIdAndUpdate(event._id, { __t: 'SignedUp' }, { new: true });
event.__t; // 'ClickedLink', update was a no-op
要更新文档的鉴别键,请使用 findOneAndUpdate()
或 updateOne()
以及 overwriteDiscriminatorKey
选项设置,如下所示。
¥To update a document's discriminator key, use findOneAndUpdate()
or updateOne()
with the overwriteDiscriminatorKey
option set as follows.
let event = new ClickedLinkEvent({ time: Date.now(), url: 'google.com' });
await event.save();
event = await ClickedLinkEvent.findByIdAndUpdate(
event._id,
{ __t: 'SignedUp' },
{ overwriteDiscriminatorKey: true, new: true }
);
event.__t; // 'SignedUp', updated discriminator key
数组中嵌入鉴别器
¥Embedded discriminators in arrays
你还可以在嵌入文档数组上定义鉴别器。嵌入的鉴别器是不同的,因为不同的鉴别器类型存储在同一文档数组(文档内)中,而不是同一集合中。换句话说,嵌入式鉴别器允许你将匹配不同结构的子文档存储在同一数组中。
¥You can also define discriminators on embedded document arrays. Embedded discriminators are different because the different discriminator types are stored in the same document array (within a document) rather than the same collection. In other words, embedded discriminators let you store subdocuments matching different schemas in the same array.
作为一般最佳实践,请确保在使用模式之前声明模式上的任何钩子。你不应在调用 discriminator()
之后再调用 pre()
或 post()
。
¥As a general best practice, make sure you declare any hooks on your schemas before you use them.
You should not call pre()
or post()
after calling discriminator()
.
const eventSchema = new Schema({ message: String },
{ discriminatorKey: 'kind', _id: false });
const batchSchema = new Schema({ events: [eventSchema] });
// `batchSchema.path('events')` gets the mongoose `DocumentArray`
// For TypeScript, use `schema.path<Schema.Types.DocumentArray>('events')`
const docArray = batchSchema.path('events');
// The `events` array can contain 2 different types of events, a
// 'clicked' event that requires an element id that was clicked...
const clickedSchema = new Schema({
element: {
type: String,
required: true
}
}, { _id: false });
// Make sure to attach any hooks to `eventSchema` and `clickedSchema`
// **before** calling `discriminator()`.
const Clicked = docArray.discriminator('Clicked', clickedSchema);
// ... and a 'purchased' event that requires the product that was purchased.
const Purchased = docArray.discriminator('Purchased', new Schema({
product: {
type: String,
required: true
}
}, { _id: false }));
const Batch = db.model('EventBatch', batchSchema);
// Create a new batch of events with different kinds
const doc = await Batch.create({
events: [
{ kind: 'Clicked', element: '#hero', message: 'hello' },
{ kind: 'Purchased', product: 'action-figure-1', message: 'world' }
]
});
assert.equal(doc.events.length, 2);
assert.equal(doc.events[0].element, '#hero');
assert.equal(doc.events[0].message, 'hello');
assert.ok(doc.events[0] instanceof Clicked);
assert.equal(doc.events[1].product, 'action-figure-1');
assert.equal(doc.events[1].message, 'world');
assert.ok(doc.events[1] instanceof Purchased);
doc.events.push({ kind: 'Purchased', product: 'action-figure-2' });
await doc.save();
assert.equal(doc.events.length, 3);
assert.equal(doc.events[2].product, 'action-figure-2');
assert.ok(doc.events[2] instanceof Purchased);
单嵌套判别器
¥Single nested discriminators
你还可以在单个嵌套子文档上定义鉴别器,类似于在子文档数组上定义鉴别器的方式。
¥You can also define discriminators on single nested subdocuments, similar to how you can define discriminators on arrays of subdocuments.
作为一般最佳实践,请确保在使用模式之前声明模式上的任何钩子。你不应在调用 discriminator()
之后再调用 pre()
或 post()
。
¥As a general best practice, make sure you declare any hooks on your schemas before you use them.
You should not call pre()
or post()
after calling discriminator()
.
const shapeSchema = Schema({ name: String }, { discriminatorKey: 'kind' });
const schema = Schema({ shape: shapeSchema });
// For TypeScript, use `schema.path<Schema.Types.Subdocument>('shape').discriminator(...)`
schema.path('shape').discriminator('Circle', Schema({ radius: String }));
schema.path('shape').discriminator('Square', Schema({ side: Number }));
const MyModel = mongoose.model('ShapeTest', schema);
// If `kind` is set to 'Circle', then `shape` will have a `radius` property
let doc = new MyModel({ shape: { kind: 'Circle', radius: 5 } });
doc.shape.radius; // 5
// If `kind` is set to 'Square', then `shape` will have a `side` property
doc = new MyModel({ shape: { kind: 'Square', side: 10 } });
doc.shape.side; // 10