Mongoose中文文档-指南之鉴别器(Discriminators)

 2018年11月21日    126     声明


Mongoose的鉴别器(Discriminator)提供了Schema间的继承机制,我们可以定义一个基础模型的schema,并通过基础模型的model.discriminator()方法来加子schema,即可实现模式的继承。

  1. model.discriminator()函数
  2. 鉴别器保存到Event模型的集合中
  3. 鉴别器的Key
  4. 添加鉴别器的Key到查询中
  5. 鉴别者复制prepost钩子
  6. 处理自定义_id字段
  7. Model.create()中使用鉴别器
  8. 数组中的嵌入式鉴别器

1. model.discriminator()函数

鉴别器是一种模式(schema)继承机制。它们使你能够在同一个基础MongoDB集合之上拥有多个具有相似模式的模型。

例如,你想要在单个集合中跟踪不同类型的事件。每个事件都有一个时间戳,但代表点击链接的事件应该有一个URL。这时,你可以使用model.discriminator()来实现此目的。它使用2个参数,模型名称(name)和鉴别器模式(schema),并返回一个模型,其模式是基本模式和鉴别器模式的并集。

var options = {discriminatorKey: 'kind'};

var eventSchema = new mongoose.Schema({time: Date}, options);
var Event = mongoose.model('Event', eventSchema);

// ClickedLinkEvent是一种特殊、具有URL的Event类型
var ClickedLinkEvent = Event.discriminator('ClickedLink',
  new mongoose.Schema({url: String}, options));

// 创建通用事件时,它不能有URL字段...
var genericEvent = new Event({time: Date.now(), url: 'google.com'});
assert.ok(!genericEvent.url);

// 但 ClickedLinkEvent 可以有
var clickedEvent =
  new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
assert.ok(clickedEvent.url);


2. 鉴别器保存到Event模型的集合中

假设你创建了另一个鉴别器来跟踪新用户注册的事件。这些SignedUpEvent实例将存储在与通用事件和ClickedLinkEvent实例相同的集合中。

var event1 = new Event({time: Date.now()});
var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});

var save = function (doc, callback) {
  doc.save(function (error, doc) {
    callback(error, doc);
  });
};

async.map([event1, event2, event3], save, function (error) {

  Event.countDocuments({}, function (error, count) {
    assert.equal(count, 3);
  });
});


3. 鉴别器的Key

Mongoose通过“鉴别器的key”告诉不同鉴别器模型之间差异,默认为__t。即,Mongoose会将一个名为__t的String类型的路径添加到模式中,用于跟踪此文档所属的标识符。

var event1 = new Event({time: Date.now()});
var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});

assert.ok(!event1.__t);
assert.equal(event2.__t, 'ClickedLink');
assert.equal(event3.__t, 'SignedUp');


4. 添加鉴别器的Key到查询中

鉴别器模型比较特殊,它们会将鉴别器Key添加到查询中。也就是说,find()count()aggregate()等以足够聪明来识别鉴别器。

var event1 = new Event({time: Date.now()});
var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});

var save = function (doc, callback) {
  doc.save(function (error, doc) {
    callback(error, doc);
  });
};

async.map([event1, event2, event3], save, function (error) {

  ClickedLinkEvent.find({}, function (error, docs) {
    assert.equal(docs.length, 1);
    assert.equal(docs[0]._id.toString(), event2._id.toString());
    assert.equal(docs[0].url, 'google.com');
  });
});


5. 鉴别者复制prepost钩子

鉴别器还会使用其基础模式的prepost中间件。但是,可以将中间件附加到鉴别器模式中,而不会影响基本模式。

var options = {discriminatorKey: 'kind'};

var eventSchema = new mongoose.Schema({time: Date}, options);
var eventSchemaCalls = 0;
eventSchema.pre('validate', function (next) {
  ++eventSchemaCalls;
  next();
});
var Event = mongoose.model('GenericEvent', eventSchema);

var clickedLinkSchema = new mongoose.Schema({url: String}, options);
var clickedSchemaCalls = 0;
clickedLinkSchema.pre('validate', function (next) {
  ++clickedSchemaCalls;
  next();
});
var ClickedLinkEvent = Event.discriminator('ClickedLinkEvent',
  clickedLinkSchema);

var event1 = new ClickedLinkEvent();
event1.validate(function() {
  assert.equal(eventSchemaCalls, 1);
  assert.equal(clickedSchemaCalls, 1);

  var generic = new Event();
  generic.validate(function() {
    assert.equal(eventSchemaCalls, 2);
    assert.equal(clickedSchemaCalls, 1);
  });
});


6. 处理自定义_id字段

鉴别器的字段是基本模式字段和鉴别器模式字段的并集,鉴别器模式的字段优先(默认的_id字段除外)。

你可以通过在鉴别器模式中将_id选项设置为false来解决这一问题,如下所示。

var options = {discriminatorKey: 'kind'};

// Base schema has a String `_id` and a Date `time`...
var eventSchema = new mongoose.Schema({_id: String, time: Date},
  options);
var Event = mongoose.model('BaseEvent', eventSchema);

var clickedLinkSchema = new mongoose.Schema({
  url: String,
  time: String
}, options);
// But the discriminator schema has a String `time`, and an implicitly added
// ObjectId `_id`.
assert.ok(clickedLinkSchema.path('_id'));
assert.equal(clickedLinkSchema.path('_id').instance, 'ObjectID');
var ClickedLinkEvent = Event.discriminator('ChildEventBad',
  clickedLinkSchema);

var event1 = new ClickedLinkEvent({ _id: 'custom id', time: '4pm' });
// Woops, clickedLinkSchema overwrites the `time` path, but **not**
// the `_id` path because that was implicitly added.
assert.ok(typeof event1._id === 'string');
assert.ok(typeof event1.time === 'string');


7. 在Model.create()中使用鉴别器

当使用Model.create()时,mongoose会从你的鉴别器键中提取正确的类型。

var Schema = mongoose.Schema;
var shapeSchema = new Schema({
  name: String
}, { discriminatorKey: 'kind' });

var Shape = db.model('Shape', shapeSchema);

var Circle = Shape.discriminator('Circle',
  new Schema({ radius: Number }));
var Square = Shape.discriminator('Square',
  new Schema({ side: Number }));

var shapes = [
  { name: 'Test' },
  { kind: 'Circle', radius: 5 },
  { kind: 'Square', side: 10 }
];
Shape.create(shapes, function(error, shapes) {
  assert.ifError(error);
  assert.ok(shapes[0] instanceof Shape);
  assert.ok(shapes[1] instanceof Circle);
  assert.equal(shapes[1].radius, 5);
  assert.ok(shapes[2] instanceof Square);
  assert.equal(shapes[2].side, 10);
});


8. 数组中的嵌入式鉴别器

还可以在嵌入文档数组上定义鉴别器。嵌入式鉴别器有所不同,因为不同的鉴别器类型存储在相同的文档数组中(在文档内)而不是相同的集合。也就是说,嵌入式鉴别器允许你在同一数组中存储与不同模式匹配的子文档。

在使用中,任何钓子应该确保在使用它们之前声明。在调用discriminator()后,就不应该在调用pre()post()

var eventSchema = new Schema({ message: String },
  { discriminatorKey: 'kind', _id: false });

var batchSchema = new Schema({ events: [eventSchema] });

// `batchSchema.path('events')` gets the mongoose `DocumentArray`
var 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...
var clickedSchema = new Schema({
  element: {
    type: String,
    required: true
  }
}, { _id: false });
// Make sure to attach any hooks to `eventSchema` and `clickedSchema`
// **before** calling `discriminator()`.
var Clicked = docArray.discriminator('Clicked', clickedSchema);

// ... and a 'purchased' event that requires the product that was purchased.
var Purchased = docArray.discriminator('Purchased', new Schema({
  product: {
    type: String,
    required: true
  }
}, { _id: false }));

var Batch = db.model('EventBatch', batchSchema);

// Create a new batch of events with different kinds
var batch = {
  events: [
    { kind: 'Clicked', element: '#hero', message: 'hello' },
    { kind: 'Purchased', product: 'action-figure-1', message: 'world' }
  ]
};

Batch.create(batch).
  then(function(doc) {
    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' });
    return doc.save();
  }).
  then(function(doc) {
    assert.equal(doc.events.length, 3);

    assert.equal(doc.events[2].product, 'action-figure-2');
    assert.ok(doc.events[2] instanceof Purchased);

    done();
  }).
  catch(done);


变更记录

  • [2018-11-21] 基于Mongoose官方文档v5.3.12首次发布