Mongoose具有不同对象方法的多类型模式

时间:2021-07-22 19:31:53

When I have a collection schema that has a multi type property. Like it can be number or string

当我有一个具有多类型属性的集合模式时。比如可以是数字或字符串

I use mongoose's mixes type for this but all validations are gone.

我使用猫鼬的混合类型,但是所有的验证都消失了。

Question:

问题:

var employeeSchema = mongoose.Schema({
    _id:false,
    title:string,
    ...
});

EmployeeSchema.methods.(....)//add some methods

var GroupSchema = ({
     visitor: {},  // visitor can be an Employee schema or a string of person name
     note: string,
     time: Date
},{collection:'group'});

How can I define visitor in groupSchema that:

如何在groupSchema中定义访问者:

  1. Validations check for matching string or Employee in saves.
  2. 在save中检查匹配的字符串或员工。
  3. When reading data from database if visitor be an Employee I can access Employee methods.
  4. 当从数据库读取数据时,如果访问者是雇员,我可以访问员工方法。

1 个解决方案

#1


3  

What you seem to be after here is really a form of "polymorphism" or how we generally implement this in a database as a "discriminator". The basics here is that one form of Object inherits from another, yet is it's own object with it's own unique properties as well as associated methods.

在这里,您所看到的实际上是一种“多态性”的形式,或者我们通常将其作为“鉴别器”在数据库中实现。这里的基础是,一种形式的对象继承了另一种形式的对象,但它是自己的对象,具有自己独特的属性和相关的方法。

This actually works out better than just distinquishing between a plain "string" and somethig more concrete as an Object, and it is fairly simple to implement. As an example:

这实际上比简单的“字符串”和更具体的对象之间的分离效果更好,而且实现起来也相当简单。作为一个例子:

var async     = require('async'),
    util      = require('util'),
    mongoose  = require('mongoose'),
    Schema    = mongoose.Schema;

mongoose.connect('mongodb://localhost/company');


function BaseSchema() {

  Schema.apply(this,arguments);

  this.add({
    name: String
  });

}

util.inherits(BaseSchema,Schema);

var personSchema = new BaseSchema();

personSchema.methods.speak = function() {
  console.log( "my name is %s",this.name );
};

var employeeSchema = new BaseSchema({
  title: String
});

employeeSchema.methods.speak = function() {
  console.log( "my name is %s, and my title is %s",this.name,this.title );
};

var groupSchema = new Schema({
  visitor: { "type": Schema.Types.ObjectId, "ref": "Person" },
  note: String,
  time: { "type": Date, "default": Date.now }
});

var Person    = mongoose.model( 'Person', personSchema ),
    Employee  = Person.discriminator( 'Employee', employeeSchema ),
    Group     = mongoose.model( 'Group', groupSchema );


async.waterfall(
  [
    // Clean data
    function(callback) {
      async.each([Person,Group],function(model,callback) {
        model.remove(callback);
      },callback);
    },

    // Add a person
    function(callback) {
      var person = new Person({ "name": "Bob" });
      person.save(function(err,person) {
        callback(err,person);
      });
    },

    // Add an employee
    function(person,callback) {
      var employee = new Employee({ "name": "Sarah", "title": "Manager" });
      employee.save(function(err,employee) {
        callback(err,person,employee);
      });
    },

    // Add person to group
    function(person,employee,callback) {
      var group = new Group({
        visitor: person
      });
      group.save(function(err) {
        callback(err,employee);
      });
    },

    // Add employee to group
    function(employee,callback) {
      var group = new Group({
        visitor: employee
      });
      group.save(function(err) {
        callback(err);
      });
    },

    // Get groups populated
    function(callback) {
      Group.find().populate('visitor').exec(function(err,group) {
        console.dir(group);
        group.forEach(function(member) {
          member.visitor.speak();
        });
        callback(err);
      });
    }

  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

And the very basic output here:

这里的基本输出是

[ { _id: 55d06d984a4690ca1f0d73ed,
    visitor: { _id: 55d06d984a4690ca1f0d73eb, name: 'Bob', __v: 0 },
    __v: 0,
    time: Sun Aug 16 2015 21:01:44 GMT+1000 (AEST) },
  { _id: 55d06d984a4690ca1f0d73ee,
    visitor:
     { _id: 55d06d984a4690ca1f0d73ec,
       name: 'Sarah',
       title: 'Manager',
       __v: 0,
       __t: 'Employee' },
    __v: 0,
    time: Sun Aug 16 2015 21:01:44 GMT+1000 (AEST) } ]
my name is Bob
my name is Sarah, and my title is Manager

In short, we have two "types" here as a "Person" and an "Employee". A "Person" is of course the base type that everyone is and therefore can have some base properties, and even methods if wanted. The "Employee" inherits from the base "Person" and therefore shares any properties and methods, as well as being able to define their own.

简而言之,我们有两种“类型”作为“人”和“员工”。“Person”当然是每个人都有的基类型,因此可以有一些基属性,如果需要,甚至可以有方法。“员工”继承了基本的“Person”,因此共享任何属性和方法,并能够定义自己的属性和方法。

When setting up the mongoose models here, these are the important lines:

在这里建立mongoose模型时,以下是重要的几点:

var Person    = mongoose.model( 'Person', personSchema ),
    Employee  = Person.discriminator( 'Employee', employeeSchema ),

Note that "Employee" does not use the standard mongoose.model contructor, but instead calls Person.discriminator. This does a special thing, where in fact the "Employee" is actually shared with the "Person" model, but with distinct information to tell mongoose that this is in fact an "Employee".

注意,“Employee”不使用标准的mongoose。模型逆变器,但是调用Person.discriminator。这做了一件特殊的事情,实际上“员工”实际上与“人”模型共享,但是有明确的信息告诉mongoose这实际上是一个“员工”。

Note also the contruction on "visitor" in the groupSchema:

还注意到群模式中关于“visitor”的构造:

  visitor: { "type": Schema.Types.ObjectId, "ref": "Person" },

As mentioned before both "Person" and "Employee" actually share the same base model as "Person", though with some specific traits. So the reference here tells mongoose to resolve the stored ObjectId values from this model.

正如前面提到的,“人”和“员工”实际上与“人”有着相同的基本模型,尽管有一些特定的特征。这里的引用告诉mongoose要从这个模型中解析存储的objective值。

The beauty of this becomes apparent when you call .populate() as is done later in the listing. As each of these references is resolved, the correct Object type is replaced for the value that was there. This becomes evident as the .speak() method is called on each object for "visitor".

当您调用.populate()时,其美妙之处在于稍后在清单中执行的操作。在解析这些引用时,将为其中的值替换正确的对象类型。这一点很明显,因为.speak()方法对每个对象调用“visitor”。

my name is Bob
my name is Sarah, and my title is Manager

There are also other great things you get as mongoose can do things such as look at "Employee" as a model and "automatically" filter out any objects that are not an employee in any query. By the same token, using the "Person" model will also show all types in there ( they are distinquished by the "__t" property and it's value ) so you can also exploit that in various query processes.

mongoose还可以做其他一些很棒的事情,比如把“Employee”看作一个模型,“自动”过滤掉任何查询中不是Employee的对象。同样,使用“Person”模型也会显示所有类型(它们会被“__t”属性和它的值所影响),因此您也可以在各种查询过程中利用它。

Embedding

So if you don't like referencing or generally just prefer to keep all the data in the "groups" collection and not bother with a separate collection for the other "people" data, then this is possible as well.

因此,如果您不喜欢引用,或者只是喜欢将所有数据保存在“组”集合中,而不为其他“人”数据使用单独的集合,那么这也是可能的。

All you really need here are a few changes to the listing, as notably in the schema definition for "visitor" as a "Mixed" type:

这里真正需要做的就是对清单进行一些更改,特别是在“visitor”的模式定义中,将其定义为“混合”类型:

  visitor: Schema.Types.Mixed,

And then rather than calling .populate() since all the data is already there, you just need to do some manual "type casting", which is actually quite simple:

而不是调用。populate()因为所有的数据都已经在那里了,你只需要做一些手工的“类型转换”,这其实很简单:

        group.forEach(function(member) {
          member.visitor = (member.visitor.hasOwnProperty("__t"))
            ? mongoose.model(member.visitor.__t)(member.visitor)
            : Person(member.visitor);
          member.visitor.speak();
        });

And then everthing works "swimmingly" with embedded data as well.

然后,everthing也能“顺畅地”使用嵌入式数据。

For a complete listing where using embedded data:

对于使用嵌入式数据的完整清单:

var async     = require('async'),
    util      = require('util'),
    mongoose  = require('mongoose'),
    Schema    = mongoose.Schema;

mongoose.connect('mongodb://localhost/company');

function BaseSchema() {

  Schema.apply(this,arguments);

  this.add({
    name: String
  });

}

util.inherits(BaseSchema,Schema);

var personSchema = new BaseSchema();

personSchema.methods.speak = function() {
  console.log( "my name is %s",this.name );
};

var employeeSchema = new BaseSchema({
  title: String
});

employeeSchema.methods.speak = function() {
  console.log( "my name is %s and my title is %s",this.name,this.title );
};

var groupSchema = new Schema({
  visitor: Schema.Types.Mixed,
  note: String,
  time: { "type": Date, "default": Date.now }
});

var Person    = mongoose.model( 'Person', personSchema, null ),
    Employee  = Person.discriminator( 'Employee', employeeSchema, null ),
    Group     = mongoose.model( 'Group', groupSchema );


async.waterfall(
  [
    // Clean data
    function(callback) {
      async.each([Person,Group],function(model,callback) {
        model.remove(callback);
      },callback);
    },

    // Add a person
    function(callback) {
      var person = new Person({ "name": "Bob" });
      callback(null,person);
    },

    // Add an employee
    function(person,callback) {
      var employee = new Employee({ "name": "Sarah", "title": "Manager" });
      callback(null,person,employee);
    },

    // Add person to group
    function(person,employee,callback) {
      var group = new Group({
        visitor: person
      });
      group.save(function(err) {
        callback(err,employee);
      });
    },

    // Add employee to group
    function(employee,callback) {
      var group = new Group({
        visitor: employee
      });
      group.save(function(err) {
        callback(err);
      });
    },

    // Get groups populated
    function(callback) {
      Group.find().exec(function(err,group) {
        console.dir(group);
        group.forEach(function(member) {
          member.visitor = (member.visitor.hasOwnProperty("__t"))
            ? mongoose.model(member.visitor.__t)(member.visitor)
            : Person(member.visitor);
          member.visitor.speak();
        });
        callback(err);
      });
    }

  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

This has the same output as above and the objects created maintain that handy "discriminator" information. So you can again exploit that much as was mentioned before.

它具有与上面相同的输出,创建的对象维护方便的“鉴别器”信息。所以你可以再次利用之前提到的东西。

#1


3  

What you seem to be after here is really a form of "polymorphism" or how we generally implement this in a database as a "discriminator". The basics here is that one form of Object inherits from another, yet is it's own object with it's own unique properties as well as associated methods.

在这里,您所看到的实际上是一种“多态性”的形式,或者我们通常将其作为“鉴别器”在数据库中实现。这里的基础是,一种形式的对象继承了另一种形式的对象,但它是自己的对象,具有自己独特的属性和相关的方法。

This actually works out better than just distinquishing between a plain "string" and somethig more concrete as an Object, and it is fairly simple to implement. As an example:

这实际上比简单的“字符串”和更具体的对象之间的分离效果更好,而且实现起来也相当简单。作为一个例子:

var async     = require('async'),
    util      = require('util'),
    mongoose  = require('mongoose'),
    Schema    = mongoose.Schema;

mongoose.connect('mongodb://localhost/company');


function BaseSchema() {

  Schema.apply(this,arguments);

  this.add({
    name: String
  });

}

util.inherits(BaseSchema,Schema);

var personSchema = new BaseSchema();

personSchema.methods.speak = function() {
  console.log( "my name is %s",this.name );
};

var employeeSchema = new BaseSchema({
  title: String
});

employeeSchema.methods.speak = function() {
  console.log( "my name is %s, and my title is %s",this.name,this.title );
};

var groupSchema = new Schema({
  visitor: { "type": Schema.Types.ObjectId, "ref": "Person" },
  note: String,
  time: { "type": Date, "default": Date.now }
});

var Person    = mongoose.model( 'Person', personSchema ),
    Employee  = Person.discriminator( 'Employee', employeeSchema ),
    Group     = mongoose.model( 'Group', groupSchema );


async.waterfall(
  [
    // Clean data
    function(callback) {
      async.each([Person,Group],function(model,callback) {
        model.remove(callback);
      },callback);
    },

    // Add a person
    function(callback) {
      var person = new Person({ "name": "Bob" });
      person.save(function(err,person) {
        callback(err,person);
      });
    },

    // Add an employee
    function(person,callback) {
      var employee = new Employee({ "name": "Sarah", "title": "Manager" });
      employee.save(function(err,employee) {
        callback(err,person,employee);
      });
    },

    // Add person to group
    function(person,employee,callback) {
      var group = new Group({
        visitor: person
      });
      group.save(function(err) {
        callback(err,employee);
      });
    },

    // Add employee to group
    function(employee,callback) {
      var group = new Group({
        visitor: employee
      });
      group.save(function(err) {
        callback(err);
      });
    },

    // Get groups populated
    function(callback) {
      Group.find().populate('visitor').exec(function(err,group) {
        console.dir(group);
        group.forEach(function(member) {
          member.visitor.speak();
        });
        callback(err);
      });
    }

  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

And the very basic output here:

这里的基本输出是

[ { _id: 55d06d984a4690ca1f0d73ed,
    visitor: { _id: 55d06d984a4690ca1f0d73eb, name: 'Bob', __v: 0 },
    __v: 0,
    time: Sun Aug 16 2015 21:01:44 GMT+1000 (AEST) },
  { _id: 55d06d984a4690ca1f0d73ee,
    visitor:
     { _id: 55d06d984a4690ca1f0d73ec,
       name: 'Sarah',
       title: 'Manager',
       __v: 0,
       __t: 'Employee' },
    __v: 0,
    time: Sun Aug 16 2015 21:01:44 GMT+1000 (AEST) } ]
my name is Bob
my name is Sarah, and my title is Manager

In short, we have two "types" here as a "Person" and an "Employee". A "Person" is of course the base type that everyone is and therefore can have some base properties, and even methods if wanted. The "Employee" inherits from the base "Person" and therefore shares any properties and methods, as well as being able to define their own.

简而言之,我们有两种“类型”作为“人”和“员工”。“Person”当然是每个人都有的基类型,因此可以有一些基属性,如果需要,甚至可以有方法。“员工”继承了基本的“Person”,因此共享任何属性和方法,并能够定义自己的属性和方法。

When setting up the mongoose models here, these are the important lines:

在这里建立mongoose模型时,以下是重要的几点:

var Person    = mongoose.model( 'Person', personSchema ),
    Employee  = Person.discriminator( 'Employee', employeeSchema ),

Note that "Employee" does not use the standard mongoose.model contructor, but instead calls Person.discriminator. This does a special thing, where in fact the "Employee" is actually shared with the "Person" model, but with distinct information to tell mongoose that this is in fact an "Employee".

注意,“Employee”不使用标准的mongoose。模型逆变器,但是调用Person.discriminator。这做了一件特殊的事情,实际上“员工”实际上与“人”模型共享,但是有明确的信息告诉mongoose这实际上是一个“员工”。

Note also the contruction on "visitor" in the groupSchema:

还注意到群模式中关于“visitor”的构造:

  visitor: { "type": Schema.Types.ObjectId, "ref": "Person" },

As mentioned before both "Person" and "Employee" actually share the same base model as "Person", though with some specific traits. So the reference here tells mongoose to resolve the stored ObjectId values from this model.

正如前面提到的,“人”和“员工”实际上与“人”有着相同的基本模型,尽管有一些特定的特征。这里的引用告诉mongoose要从这个模型中解析存储的objective值。

The beauty of this becomes apparent when you call .populate() as is done later in the listing. As each of these references is resolved, the correct Object type is replaced for the value that was there. This becomes evident as the .speak() method is called on each object for "visitor".

当您调用.populate()时,其美妙之处在于稍后在清单中执行的操作。在解析这些引用时,将为其中的值替换正确的对象类型。这一点很明显,因为.speak()方法对每个对象调用“visitor”。

my name is Bob
my name is Sarah, and my title is Manager

There are also other great things you get as mongoose can do things such as look at "Employee" as a model and "automatically" filter out any objects that are not an employee in any query. By the same token, using the "Person" model will also show all types in there ( they are distinquished by the "__t" property and it's value ) so you can also exploit that in various query processes.

mongoose还可以做其他一些很棒的事情,比如把“Employee”看作一个模型,“自动”过滤掉任何查询中不是Employee的对象。同样,使用“Person”模型也会显示所有类型(它们会被“__t”属性和它的值所影响),因此您也可以在各种查询过程中利用它。

Embedding

So if you don't like referencing or generally just prefer to keep all the data in the "groups" collection and not bother with a separate collection for the other "people" data, then this is possible as well.

因此,如果您不喜欢引用,或者只是喜欢将所有数据保存在“组”集合中,而不为其他“人”数据使用单独的集合,那么这也是可能的。

All you really need here are a few changes to the listing, as notably in the schema definition for "visitor" as a "Mixed" type:

这里真正需要做的就是对清单进行一些更改,特别是在“visitor”的模式定义中,将其定义为“混合”类型:

  visitor: Schema.Types.Mixed,

And then rather than calling .populate() since all the data is already there, you just need to do some manual "type casting", which is actually quite simple:

而不是调用。populate()因为所有的数据都已经在那里了,你只需要做一些手工的“类型转换”,这其实很简单:

        group.forEach(function(member) {
          member.visitor = (member.visitor.hasOwnProperty("__t"))
            ? mongoose.model(member.visitor.__t)(member.visitor)
            : Person(member.visitor);
          member.visitor.speak();
        });

And then everthing works "swimmingly" with embedded data as well.

然后,everthing也能“顺畅地”使用嵌入式数据。

For a complete listing where using embedded data:

对于使用嵌入式数据的完整清单:

var async     = require('async'),
    util      = require('util'),
    mongoose  = require('mongoose'),
    Schema    = mongoose.Schema;

mongoose.connect('mongodb://localhost/company');

function BaseSchema() {

  Schema.apply(this,arguments);

  this.add({
    name: String
  });

}

util.inherits(BaseSchema,Schema);

var personSchema = new BaseSchema();

personSchema.methods.speak = function() {
  console.log( "my name is %s",this.name );
};

var employeeSchema = new BaseSchema({
  title: String
});

employeeSchema.methods.speak = function() {
  console.log( "my name is %s and my title is %s",this.name,this.title );
};

var groupSchema = new Schema({
  visitor: Schema.Types.Mixed,
  note: String,
  time: { "type": Date, "default": Date.now }
});

var Person    = mongoose.model( 'Person', personSchema, null ),
    Employee  = Person.discriminator( 'Employee', employeeSchema, null ),
    Group     = mongoose.model( 'Group', groupSchema );


async.waterfall(
  [
    // Clean data
    function(callback) {
      async.each([Person,Group],function(model,callback) {
        model.remove(callback);
      },callback);
    },

    // Add a person
    function(callback) {
      var person = new Person({ "name": "Bob" });
      callback(null,person);
    },

    // Add an employee
    function(person,callback) {
      var employee = new Employee({ "name": "Sarah", "title": "Manager" });
      callback(null,person,employee);
    },

    // Add person to group
    function(person,employee,callback) {
      var group = new Group({
        visitor: person
      });
      group.save(function(err) {
        callback(err,employee);
      });
    },

    // Add employee to group
    function(employee,callback) {
      var group = new Group({
        visitor: employee
      });
      group.save(function(err) {
        callback(err);
      });
    },

    // Get groups populated
    function(callback) {
      Group.find().exec(function(err,group) {
        console.dir(group);
        group.forEach(function(member) {
          member.visitor = (member.visitor.hasOwnProperty("__t"))
            ? mongoose.model(member.visitor.__t)(member.visitor)
            : Person(member.visitor);
          member.visitor.speak();
        });
        callback(err);
      });
    }

  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

This has the same output as above and the objects created maintain that handy "discriminator" information. So you can again exploit that much as was mentioned before.

它具有与上面相同的输出,创建的对象维护方便的“鉴别器”信息。所以你可以再次利用之前提到的东西。