web-dev-qa-db-fra.com

Mongoose: étendre les schémas

Actuellement, j'ai deux schémas presque identiques:

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},

});

Et

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},

});

Leur seule différence réside dans la validation: les utilisateurs n'ont pas besoin de prénom, nom ou téléphone. Les administrateurs doivent toutefois avoir ces propriétés définies.

Malheureusement, le code ci-dessus n'est pas très sec, car ils sont presque identiques. Par conséquent, je me demande s’il est possible de construire une adminSchema basée sur la userSchema. Par exemple.:

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

Évidemment, c'est juste un pseudocode. Est-ce que quelque chose comme ça est possible?

Une autre question très similaire est de savoir s'il est possible de créer un nouveau schéma basé sur un autre et d'y ajouter des propriétés supplémentaires. Par exemple:

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

Certaines personnes ont dans d'autres endroits suggéré d'utiliser utils.inherits pour étendre les schémas. Une autre méthode simple consiste à configurer simplement un objet avec des paramètres et à en créer des schémas, comme suit:

var settings = {
  one: Number
};

new Schema(settings);

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

C'est un peu moche cependant, puisque vous modifiez le même objet. J'aimerais aussi pouvoir étendre les plugins, les méthodes, etc. Ainsi, ma méthode préférée est la suivante:

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

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

  return schema;
}

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

Ce qui arrive à répondre à votre deuxième question que oui, vous pouvez add() champs. Donc, pour modifier certaines propriétés du schéma, une version modifiée de la fonction ci-dessus résoudrait votre problème:

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;
}
10
Andreas Hultgren

Mongoose 3.8.1 prend désormais en charge les discriminateurs. Un exemple, à partir d'ici: 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);
27
regretoverflow

Vous pouvez étendre l'original Schema # obj:

const AdminSchema = new mongoose.Schema ({}, Object.assign (UserSchema.obj, {...}))

Exemple:

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

Ou utilisez ce module npm avec la même approche:

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}
});

Vérifiez le dépôt github https://github.com/doasync/mongoose-extend-schema

3
Do Async

Je viens de publier un module mongoose-super npm . Bien que j'ai fait quelques essais, il en est encore au stade expérimental. Je suis intéressé de savoir si cela fonctionne bien pour les applications de mes collègues SO utilisateurs!

Le module fournit une fonction de commodité inherit () qui renvoie un modèle Mongoose.js enfant basé sur un modèle parent et une extension de schéma enfant. Il augmente également les modèles avec une méthode super () pour appeler des méthodes de modèle parent. J'ai ajouté cette fonctionnalité parce que c'est quelque chose qui m'a échappé dans d'autres bibliothèques d'extension/d'héritage. 

La fonction de commodité héritée utilise simplement la méthode discriminator .

2
Visionscaper

Pour ajouter à cette discussion, vous pouvez également remplacer mongoose.Schema par une définition de schéma de base personnalisée. Pour la compatibilité du code, ajoutez l’instruction if permettant à un schéma d’être instancié sans new. Bien que cela puisse être pratique, réfléchissez-y à deux fois avant de le faire dans un paquet public.

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
});
2
Adam Lockhart

Toutes ces réponses semblent plutôt inutilement compliquées, avec des fonctions d'assistance d'extension ou des méthodes d'extension appliquées au schéma ou à l'aide de plugins/discriminateurs. J'ai utilisé à la place la solution suivante qui est simple, propre et sur laquelle il est facile de travailler. Il définit un schéma directeur pour le schéma de base, puis les schémas réels sont générés à l'aide du schéma directeur:

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;

Vous pouvez utiliser le plan directeur pour le schéma d'origine tel quel et utiliser Object.assign pour étendre le plan à votre guise pour d'autres schémas, sans modifier le même objet.

1
Adam Reis

Vous pouvez créer une fonction Usine de schéma acceptant une définition de schéma et des options de schéma optional, qui fusionne ensuite la définition et les options de schéma passées avec les champs et options de schéma que vous souhaitez partager entre schémas .. ceci (en supposant que vous souhaitiez partager ou étendre un schéma pour lequel les champs email et is_verified et l'option timestamps sont activés):

// 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; 

La fonction SchemaFactory peut ensuite être appelée avec:

// 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
});

Désormais, les variables UserSchema et AdminSchema contiendront à la fois les champs email et is_verified et l'activation de l'option timestamps, ainsi que les champs de schéma et les options que vous aurez transmis.

0
darthchudi

Je n'avais pas besoin de discrimination, car j'essayais de toute façon d'étendre les schémas de sous-document qui sont stockés en tant que partie d'un document parent.

Ma solution consistait à ajouter une méthode "extend" au schéma qui est le schéma de base, afin que vous puissiez utiliser le schéma de base lui-même ou générer un nouveau schéma basé sur celui-ci.

Le code ES6 suit:

'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;

Vous pouvez maintenant utiliser ce schéma tel quel ou le "prolonger" au besoin:

let BazSchema = FooSchema.extend({baz: Number});

Dans ce cas, l’extension crée une nouvelle définition de schéma.

0
Adam Reis