model.discriminator() 函数 - 图1mongoose

model.discriminator() 函数 - 图2mongoose

[

model.discriminator() 函数 ](#model-discriminator-函数)

Discriminator 是一种 schema 继承机制。 他允许你在相同的底层 MongoDB collection 上 使用部分重叠的 schema 建立多个 model。

假设你要在单个 collection 中记录多种 event, 每个 event 都有时间戳字段,但是 click 事件还有 URL 字段, 这时你可以用 model.discriminator() 实现上述要求。 此函数接受 2 个参数,model 名称和 discriminator schema, 返回的 model 结合了原 model 的 schema 和 discriminator schema。

  1. var options = {discriminatorKey: 'kind'};
  2. var eventSchema = new mongoose.Schema({time: Date}, options);
  3. var Event = mongoose.model('Event', eventSchema);
  4. // ClickedLinkEvent 是一个有 URL 的特别 event
  5. var ClickedLinkEvent = Event.discriminator('ClickedLink',
  6. new mongoose.Schema({url: String}, options));
  7. // 当你创建通用 event,他将没有 URL 字段...
  8. var genericEvent = new Event({time: Date.now(), url: 'google.com'});
  9. assert.ok(!genericEvent.url);
  10. // 但是 ClickedLinkEvent 可以有
  11. var clickedEvent =
  12. new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
  13. assert.ok(clickedEvent.url);

[

Discriminator 储存在 Event model 的 collection ](#discriminator-储存在-event-model-的-collection)

现在假设你要创建另一个 discriminator,记录用户注册 event。 SignedUpEvent 实例将跟 通用 events 和 ClickedLinkEvent 实例 一样储存在同一个 collection。

  1. var event1 = new Event({time: Date.now()});
  2. var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
  3. var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});
  4. var save = function (doc, callback) {
  5. doc.save(function (error, doc) {
  6. callback(error, doc);
  7. });
  8. };
  9. async.map([event1, event2, event3], save, function (error) {
  10. Event.count({}, function (error, count) {
  11. assert.equal(count, 3);
  12. });
  13. });

[

Discriminator keys ](#discriminator-keys)

Mongoose 通过 'discriminator key' 识别两个不同的 discriminator, 这个值默认是 __t 。Mongoose 自动在你的 schema 添加 __t 字段, 记录你的 document 是哪个 discriminator 的实例。

  1. var event1 = new Event({time: Date.now()});
  2. var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
  3. var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});
  4. assert.ok(!event1.__t);
  5. assert.equal(event2.__t, 'ClickedLink');
  6. assert.equal(event3.__t, 'SignedUp');

[

Discriminator 在查询中添加 discriminator key ](#discriminator-在查询中添加-discriminator-key)

Discriminator model 的特别之处在于:他们会把 discriminator key 附到 query 上。换句话说,find(), count(), aggregate() 等方法 都能适配 discriminators。

  1. var event1 = new Event({time: Date.now()});
  2. var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
  3. var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});
  4. var save = function (doc, callback) {
  5. doc.save(function (error, doc) {
  6. callback(error, doc);
  7. });
  8. };
  9. async.map([event1, event2, event3], save, function (error) {
  10. ClickedLinkEvent.find({}, function (error, docs) {
  11. assert.equal(docs.length, 1);
  12. assert.equal(docs[0]._id.toString(), event2._id.toString());
  13. assert.equal(docs[0].url, 'google.com');
  14. });
  15. });

[

Discriminator 复制 pre / post 钩子 ](#discriminator-复制-pre-/-post-钩子)

Discriminator 会继承他的基础 schema 的 pre 和 post 中间件。 不过,你也可以为 discriminator 添加中间件,这不回影响到基础 schema。

  1. var options = {discriminatorKey: 'kind'};
  2. var eventSchema = new mongoose.Schema({time: Date}, options);
  3. var eventSchemaCalls = 0;
  4. eventSchema.pre('validate', function (next) {
  5. ++eventSchemaCalls;
  6. next();
  7. });
  8. var Event = mongoose.model('GenericEvent', eventSchema);
  9. var clickedLinkSchema = new mongoose.Schema({url: String}, options);
  10. var clickedSchemaCalls = 0;
  11. clickedLinkSchema.pre('validate', function (next) {
  12. ++clickedSchemaCalls;
  13. next();
  14. });
  15. var ClickedLinkEvent = Event.discriminator('ClickedLinkEvent',
  16. clickedLinkSchema);
  17. var event1 = new ClickedLinkEvent();
  18. event1.validate(function() {
  19. assert.equal(eventSchemaCalls, 1);
  20. assert.equal(clickedSchemaCalls, 1);
  21. var generic = new Event();
  22. generic.validate(function() {
  23. assert.equal(eventSchemaCalls, 2);
  24. assert.equal(clickedSchemaCalls, 1);
  25. });
  26. });

[

处理自定义 _id 字段 ](#处理自定义-_id-字段)

Discriminator 的字段是基础 schema 加 discriminator schema , 并且以 discriminator schema 的字段优先。 但有一个例外,_id 字段。

You can work around this by setting the _id option to false in the discriminator schema as shown below.

  1. var options = {discriminatorKey: 'kind'};
  2. // 基础 schema 有字符串格式的 `_id` 字段和 Data 格式的 `time` 字段...
  3. var eventSchema = new mongoose.Schema({_id: String, time: Date},
  4. options);
  5. var Event = mongoose.model('BaseEvent', eventSchema);
  6. var clickedLinkSchema = new mongoose.Schema({
  7. url: String,
  8. time: String
  9. }, options);
  10. // 但是 Discriminator schema 有字符串格式的 `time`,并且有
  11. // 隐式添加的 ObjectId 格式的 `_id`
  12. assert.ok(clickedLinkSchema.path('_id'));
  13. assert.equal(clickedLinkSchema.path('_id').instance, 'ObjectID');
  14. var ClickedLinkEvent = Event.discriminator('ChildEventBad',
  15. clickedLinkSchema);
  16. var event1 = new ClickedLinkEvent({ _id: 'custom id', time: '4pm' });
  17. // 问题来了,clickedLinkSchema 重写了 `time` 路径,但是**没有**
  18. // 重写 `_id` 路径,因为已经隐式添加(没看懂)
  19. assert.ok(typeof event1._id === 'string');
  20. assert.ok(typeof event1.time === 'string');

[

discriminator 与 Model.create() ](#discriminator-与-model-create)

当你使用 Model.create(),Mongoose 会自动帮你适配 discriminator key ~

  1. var Schema = mongoose.Schema;
  2. var shapeSchema = new Schema({
  3. name: String
  4. }, { discriminatorKey: 'kind' });
  5. var Shape = db.model('Shape', shapeSchema);
  6. var Circle = Shape.discriminator('Circle',
  7. new Schema({ radius: Number }));
  8. var Square = Shape.discriminator('Square',
  9. new Schema({ side: Number }));
  10. var shapes = [
  11. { name: 'Test' },
  12. { kind: 'Circle', radius: 5 },
  13. { kind: 'Square', side: 10 }
  14. ];
  15. Shape.create(shapes, function(error, shapes) {
  16. assert.ifError(error);
  17. // 重点看这里
  18. assert.ok(shapes[0] instanceof Shape);
  19. assert.ok(shapes[1] instanceof Circle);
  20. assert.equal(shapes[1].radius, 5);
  21. assert.ok(shapes[2] instanceof Square);
  22. assert.equal(shapes[2].side, 10);
  23. });

[

数组中的嵌套 discriminator ](#数组中的嵌套-discriminator)

你也可以为嵌套文档数组定义 discriminator。 嵌套 discriminator 的特点是:不同 discriminator 类型储存在相同的文档而不是同一个 mongoDB collection。 换句话说,嵌套 discriminator 让你 在同一个数组储存符合不同 schema 的子文档。

最佳实践:确保你声明了钩子再使用他们。 你不应当在调用 discriminator() 之后调用 pre()post()

  1. var eventSchema = new Schema({ message: String },
  2. { discriminatorKey: 'kind', _id: false });
  3. var batchSchema = new Schema({ events: [eventSchema] });
  4. // `batchSchema.path('events')` gets the mongoose `DocumentArray`
  5. var docArray = batchSchema.path('events');
  6. // 这个 `events` 数组可以包含 2 种不同的 event 类型,
  7. // 'clicked' event that requires an element id that was clicked...
  8. var clickedSchema = new Schema({
  9. element: {
  10. type: String,
  11. required: true
  12. }
  13. }, { _id: false });
  14. // 确定在调用 `discriminator()` **之前**
  15. // 对 `eventSchema` 和 `clickedSchema` 赋予钩子
  16. var Clicked = docArray.discriminator('Clicked', clickedSchema);
  17. // ... and a 'purchased' event that requires the product that was purchased.
  18. var Purchased = docArray.discriminator('Purchased', new Schema({
  19. product: {
  20. type: String,
  21. required: true
  22. }
  23. }, { _id: false }));
  24. var Batch = db.model('EventBatch', batchSchema);
  25. // Create a new batch of events with different kinds
  26. var batch = {
  27. events: [
  28. { kind: 'Clicked', element: '#hero', message: 'hello' },
  29. { kind: 'Purchased', product: 'action-figure-1', message: 'world' }
  30. ]
  31. };
  32. Batch.create(batch).
  33. then(function(doc) {
  34. assert.equal(doc.events.length, 2);
  35. assert.equal(doc.events[0].element, '#hero');
  36. assert.equal(doc.events[0].message, 'hello');
  37. assert.ok(doc.events[0] instanceof Clicked);
  38. assert.equal(doc.events[1].product, 'action-figure-1');
  39. assert.equal(doc.events[1].message, 'world');
  40. assert.ok(doc.events[1] instanceof Purchased);
  41. doc.events.push({ kind: 'Purchased', product: 'action-figure-2' });
  42. return doc.save();
  43. }).
  44. then(function(doc) {
  45. assert.equal(doc.events.length, 3);
  46. assert.equal(doc.events[2].product, 'action-figure-2');
  47. assert.ok(doc.events[2] instanceof Purchased);
  48. done();
  49. }).
  50. catch(done);

[

检索数组中的嵌套 discriminator ](#检索数组中的嵌套-discriminator)

检索嵌套 discriminator

  1. var singleEventSchema = new Schema({ message: String },
  2. { discriminatorKey: 'kind', _id: false });
  3. var eventListSchema = new Schema({ events: [singleEventSchema] });
  4. var subEventSchema = new Schema({
  5. sub_events: [singleEventSchema]
  6. }, { _id: false });
  7. var SubEvent = subEventSchema.path('sub_events').discriminator('SubEvent', subEventSchema)
  8. eventListSchema.path('events').discriminator('SubEvent', subEventSchema);
  9. var Eventlist = db.model('EventList', eventListSchema);
  10. // Create a new batch of events with different kinds
  11. var list = {
  12. events: [
  13. { kind: 'SubEvent', sub_events: [{kind:'SubEvent', sub_events:[], message:'test1'}], message: 'hello' },
  14. { kind: 'SubEvent', sub_events: [{kind:'SubEvent', sub_events:[{kind:'SubEvent', sub_events:[], message:'test3'}], message:'test2'}], message: 'world' }
  15. ]
  16. };
  17. Eventlist.create(list).
  18. then(function(doc) {
  19. assert.equal(doc.events.length, 2);
  20. assert.equal(doc.events[0].sub_events[0].message, 'test1');
  21. assert.equal(doc.events[0].message, 'hello');
  22. assert.ok(doc.events[0].sub_events[0] instanceof SubEvent);
  23. assert.equal(doc.events[1].sub_events[0].sub_events[0].message, 'test3');
  24. assert.equal(doc.events[1].message, 'world');
  25. assert.ok(doc.events[1].sub_events[0].sub_events[0] instanceof SubEvent);
  26. doc.events.push({kind:'SubEvent', sub_events:[{kind:'SubEvent', sub_events:[], message:'test4'}], message:'pushed'});
  27. return doc.save();
  28. }).
  29. then(function(doc) {
  30. assert.equal(doc.events.length, 3);
  31. assert.equal(doc.events[2].message, 'pushed');
  32. assert.ok(doc.events[2].sub_events[0] instanceof SubEvent);
  33. done();
  34. }).
  35. catch(done);