Mongoose: расширяющие схемы
В настоящее время у меня есть две почти идентичные схемы:
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},
});
и
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},
});
Единственное различие заключается в проверке: пользователям не нужно имя, фамилия или телефон. Однако админы должны определять эти свойства.
К сожалению, приведенный выше код не очень СУХОЙ, так как он почти идентичен. Поэтому мне интересно, можно ли построить adminSchema
на основе userSchema
. Например:.
var adminSchema = mongoose.Schema(userSchema);
adminSchema.change('firstname', {required: true});
adminSchema.change('lastname', {required: true});
adminSchema.change('phone', {required: true});
Очевидно, что это просто псевдокод. Возможно ли подобное?
Другой очень похожий вопрос заключается в том, можно ли создать новую схему на основе другой и добавить к ней еще несколько свойств. Например:
var adminSchema = mongoose.Schema(userSchema);
adminSchema.add(adminPower: Number);
Ответы
Ответ 1
Некоторые люди в других местах предложили использовать utils.inherits для расширения схем. Еще один простой способ - просто настроить объект с настройками и создать из него Схемы, например:
var settings = {
one: Number
};
new Schema(settings);
settings.two = Number;
new Schema(settings);
Это немного уродливо, потому что вы изменяете один и тот же объект. Также я хотел бы иметь возможность расширять плагины и методы и т.д. Таким образом, мой предпочтительный метод следующий:
function UserSchema (add) {
var schema = new Schema({
someField: String
});
if(add) {
schema.add(add);
}
return schema;
}
var userSchema = UserSchema();
var adminSchema = UserSchema({
anotherField: String
});
Что ответит на ваш второй вопрос: да, вы можете add()
поля. Поэтому, чтобы изменить некоторые свойства Схемы, модифицированная версия вышеуказанной функции решит вашу проблему:
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;
}
Ответ 2
Mongoose 3.8.1 теперь поддерживает дискриминаторы. Пример отсюда: 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);
Ответ 3
Чтобы добавить к этому обсуждению, вы также можете переопределить mongoose.Schema с определением пользовательской базовой схемы. Для совместимости кода добавьте оператор if, который позволяет создать экземпляр схемы без new
. Хотя это может быть удобно, подумайте дважды, прежде чем делать это в общедоступном пакете.
var Schema = mongoose.Schema;
var BaseSyncSchema = function(obj, options) {
if (!(this instanceof BaseSyncSchema))
return new BaseSyncSchema(obj, options);
Schema.apply(this, arguments);
this.methods.update = function() {
this.updated = new Date();
};
this.add({
updated: Date
});
};
util.inherits(BaseSyncSchema, Schema);
// Edit!!!
// mongoose.Schema = BaseSyncSchema; <-- Does not work in mongoose 4
// Do this instead:
Object.defineProperty(mongoose, "Schema", {
value: BaseSyncSchema,
writable: false
});
Ответ 4
Я только что опубликовал mongoose-super npm module. Хотя я и проводил некоторые испытания, он все еще находится на экспериментальной стадии. Мне интересно узнать, хорошо ли это работает для приложений моих пользователей SO!!
Модуль предоставляет функцию удобства inherit(), которая возвращает дочернюю модель Mongoose.js на основе родительской модели и расширения дочерней схемы. Он также дополняет модели с помощью метода super() для вызова методов родительской модели. Я добавил эту функциональность, потому что это то, что я пропустил в других библиотеках расширения/наследования.
Функция удобства наследования просто использует метод дискриминатор.
Ответ 5
Вы можете расширить оригинальную схему # obj:
const AdminSchema = new mongoose.Schema({}, Object.assign(UserSchema.obj, {...}))
Пример:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
email: {type: String, unique: true, required: true},
passwordHash: {type: String, required: true},
firstname: {type: String},
lastname: {type: String},
phone: {type: String}
});
// Extend function
const extend = (Schema, obj) => (
new mongoose.Schema(
Object.assign({}, Schema.obj, obj)
)
);
// Usage:
const AdminUserSchema = extend(UserSchema, {
firstname: {type: String, required: true},
lastname: {type: String, required: true},
phone: {type: String, required: true}
});
const User = mongoose.model('users', UserSchema);
const AdminUser = mongoose.model('admins', AdminUserSchema);
const john = new User({
email: '[email protected]',
passwordHash: 'bla-bla-bla',
firstname: 'John'
});
john.save();
const admin = new AdminUser({
email: '[email protected]',
passwordHash: 'bla-bla-bla',
firstname: 'Henry',
lastname: 'Hardcore',
// phone: '+555-5555-55'
});
admin.save();
// Oops! Error 'phone' is required
Или используйте этот модуль npm с тем же подходом:
const extendSchema = require('mongoose-extend-schema'); // not 'mongoose-schema-extend'
const UserSchema = new mongoose.Schema({
firstname: {type: String},
lastname: {type: String}
});
const ClientSchema = extendSchema(UserSchema, {
phone: {type: String, required: true}
});
Проверьте github repo https://github.com/doasync/mongoose-extend-schema
Ответ 6
Все эти ответы кажутся излишне сложными, с вспомогательными функциями расширения или расширенными методами, применяемыми к схеме или с использованием плагинов/дискриминаторов. Вместо этого я использовал следующее решение, которое просто, чисто и легко работать. Он определяет схему базовой схемы, а затем фактическая схема строится с использованием чертежа:
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;
Вы можете использовать схему исходной схемы как есть, и используя Object.assign
, вы можете расширить проект любым способом, который вам нравится для других схем, без изменения одного и того же объекта.
Ответ 7
Мне не требовалась дискриминация, так как я пытался в любом случае продлить схему субдопинга, которые хранятся как часть родительского документа.
Мое решение состояло в том, чтобы добавить метод расширения к схеме, являющейся базовой схемой, так что вы можете либо использовать базовую схему, либо создать на ней новую схему.
Код ES6 следует:
'use strict';
//Dependencies
let Schema = require('mongoose').Schema;
//Schema generator
function extendFooSchema(fields, _id = false) {
//Extend default fields with given fields
fields = Object.assign({
foo: String,
bar: String,
}, fields || {});
//Create schema
let FooSchema = new Schema(fields, {_id});
//Add methods/options and whatnot
FooSchema.methods.bar = function() { ... };
//Return
return FooSchema;
}
//Create the base schema now
let FooSchema = extendFooSchema(null, false);
//Expose generator method
FooSchema.extend = extendFooSchema;
//Export schema
module.exports = FooSchema;
Теперь вы можете использовать эту схему как есть или "расширять" ее по мере необходимости:
let BazSchema = FooSchema.extend({baz: Number});
Расширение в этом случае создает совершенно новое определение схемы.
Ответ 8
Вы можете создать функцию фабрики схем, которая принимает определение схемы и необязательные параметры схемы, которая затем объединяет переданные в определении схемы и параметры с полями и параметрами схемы, которые вы хотите использовать в схемах. Пример, иллюстрирующий это (предполагается, что вы хотите поделиться или расширить схему с полями email
и is_verified
и включенной опцией timestamps
):
// 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;
Функция SchemaFactory
может затем вызываться с помощью:
// 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
});
Теперь UserSchema
и AdminSchema
будут содержать поля email
и is_verified
а также активировать опцию timestamps
вместе с полями схемы и опциями, которые вы передаете.