鉴别器

¥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