Mongoose: extending schemas

2020-01-31 01:08发布

Currently I have two almost identical schemas:

var userSchema = mongoose.Schema({

    email: {type: String, unique: true, required: true, validate: emailValidator},
    passwordHash: {type: String, required: true},

    firstname: {type: String, validate: firstnameValidator},
    lastname: {type: String, validate: lastnameValidator},
    phone: {type: String, validate: phoneValidator},

});

And

var adminSchema = mongoose.Schema({

    email: {type: String, unique: true, required: true, validate: emailValidator},
    passwordHash: {type: String, required: true},

    firstname: {type: String, validate: firstnameValidator, required: true},
    lastname: {type: String, validate: lastnameValidator, required: true},
    phone: {type: String, validate: phoneValidator, required: true},

});

Their only difference is in validation: Users do not need a firstname, lastname or phone. Admins however must have these properties defined.

Unfortunately the above code is not very DRY, as they're almost identical. Therefore I am wondering if it is possible to build an adminSchema based on the userSchema. E.g.:

var adminSchema = mongoose.Schema(userSchema);
adminSchema.change('firstname', {required: true});
adminSchema.change('lastname', {required: true});
adminSchema.change('phone', {required: true});

Obviously that's just pseudocode. Is something like this possible?

Another very similar question is if it is possible to create a new schema based on another, and add some more properties to it. For example:

var adminSchema = mongoose.Schema(userSchema);
    adminSchema.add(adminPower: Number);

9条回答
\"骚年 ilove
2楼-- · 2020-01-31 01:29

All of these answers seem rather needlessly complicated, with extension helper functions or extend methods applied to the schema's or using plugins/discriminators. I've used the following solution instead which is simple, clean and easy to work with. It defines a blueprint for the base schema, and then the actual schema's are built using the blueprint:

foo.blueprint.js

module.exports = {
  schema: {
    foo: String,
    bar: Number,
  },
  methods: {
    fooBar() {
      return 42;
    },
  }
};

foo.schema.js

const {schema, methods} = require('./foo.blueprint');
const {Schema} = require('mongoose');
const FooSchema = new Schema(foo);
Object.assign(FooSchema.methods, methods);
module.exports = FooSchema;

bar.schema.js

const {schema, methods} = require('./foo.blueprint');
const {Schema} = require('mongoose');
const BarSchema = new Schema(Object.assign({}, schema, {
  bar: String,
  baz: Boolean,
}));
Object.assign(BarSchema.methods, methods);
module.exports = BarSchema;

You can use the blueprint for the original schema as is, and using Object.assign you can extend the blueprint in any way you like for other schema's, without modifying the same object.

查看更多
叛逆
3楼-- · 2020-01-31 01:31

You can create a Schema Factory function that accepts a Schema definition and optional schema options, which then merges the passed in Schema definition and options with the Schema fields and options which you want to share across schemas. Example illustrating this (assuming you want to share or extend a schema that has the fields email and is_verified and the timestamps option enabled):

// schemaFactory.js
const mongoose = require('mongoose');

const SchemaFactory = (schemaDefinition, schemaOptions) => {
  return new mongoose.Schema({
    {
      email: {type: String, required: true},
      is_verified: {type: Boolean, default: false},
      // spread/merge passed in schema definition
      ...schemaDefinition
    }
  }, {
    timestamps: true,
    // spread/merge passed in schema options
    ...schemaOptions
  })
}
module.exports = SchemaFactory; 

The SchemaFactory function can then be called with:

// schemas.js
const SchemaFactory = require("./schemaFactory.js")

const UserSchema = SchemaFactory({
  first_name: String,
  password: {type: String, required: true}
});

const AdminSchema = SchemaFactory({
  role: {type: String, required: true}
}, {
  // we can pass in schema options to the Schema Factory
  strict: false
});

Now the UserSchema and AdminSchema will contain both the email and is_verified field as well as have the timestamps option enabled, along with the schema fields and options you pass along.

查看更多
Luminary・发光体
4楼-- · 2020-01-31 01:35

Mongoose 3.8.1 now has support for Discriminators. A sample, from here: http://mongoosejs.com/docs/api.html#model_Model.discriminator

function BaseSchema() {
  Schema.apply(this, arguments);

  this.add({
    name: String,
    createdAt: Date
  });
}
util.inherits(BaseSchema, Schema);

var PersonSchema = new BaseSchema();
var BossSchema = new BaseSchema({ department: String });

var Person = mongoose.model('Person', PersonSchema);
var Boss = Person.discriminator('Boss', BossSchema);
查看更多
我命由我不由天
5楼-- · 2020-01-31 01:39

The simplest way for extending mongoose schema

import { model, Schema } from 'mongoose';

const ParentSchema = new Schema({
  fromParent: Boolean
});

const ChildSchema = new Schema({
  ...ParentSchema.obj,
  fromChild: Boolean // new properties come up here
});

export const Child = model('Child', ChildSchema);
查看更多
时光不老,我们不散
6楼-- · 2020-01-31 01:40

I just published a mongoose-super npm module. Although I did some testing, it is still in an experimental stage. I'm interested to know if it works well for the applications of my fellow SO users!

The module provides a inherit() convenience function that returns a child Mongoose.js model based on a parent model and a child schema extension. It also augments models with a super() method to call parent model methods. I added this functionality because it is something I missed in other extension/inheritance libraries.

The inherit convenience function simply uses the discriminator method.

查看更多
孤傲高冷的网名
7楼-- · 2020-01-31 01:44

Some people have in other places suggested using utils.inherits to extend schemas. Another simple way would be to simply set up an object with settings and create Schemas from it, like so:

var settings = {
  one: Number
};

new Schema(settings);

settings.two = Number;
new Schema(settings);

It's a bit ugly though, since you're modifying the same object. Also I'd like to be able to extend plugins and methods etc. Thus my preferred method is the following:

function UserSchema (add) {
  var schema = new Schema({
    someField: String
  });

  if(add) {
    schema.add(add);
  }

  return schema;
}

var userSchema = UserSchema();
var adminSchema = UserSchema({
  anotherField: String
});

Which happens to answer your second question that yes, you can add() fields. So to modify some properties of the Schema, a modified version of the above function would solve your problem:

function UserSchema (add, nameAndPhoneIsRequired) {
  var schema = new Schema({
    //...
    firstname: {type: String, validate: firstnameValidator, required: nameAndPhoneIsRequired},
    lastname: {type: String, validate: lastnameValidator, required: nameAndPhoneIsRequired},
    phone: {type: String, validate: phoneValidator, required: nameAndPhoneIsRequired},
  });

  if(add) {
    schema.add(add);
  }

  return schema;
}
查看更多
登录 后发表回答