阿里云主机折上折
  • 微信号
Current Site:Index > Model inheritance and extension

Model inheritance and extension

Author:Chuan Chen 阅读数:24862人阅读 分类: MongoDB

Basic Concepts of Model Inheritance

In Mongoose, model inheritance allows developers to create new models based on existing ones while retaining the properties and methods of the parent model. This mechanism is highly practical in database design, especially when multiple collections share the same base fields. Mongoose provides two main inheritance approaches: Prototypal Inheritance and Class Inheritance.

Prototypal inheritance is implemented using the add method of a Schema:

const baseSchema = new mongoose.Schema({
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

const userSchema = new mongoose.Schema({
  username: String,
  email: String
});

userSchema.add(baseSchema);

Class inheritance follows a more traditional OOP pattern:

class BaseEntity {
  constructor() {
    this.createdAt = new Date();
    this.updatedAt = new Date();
  }
}

class User extends BaseEntity {
  constructor(username, email) {
    super();
    this.username = username;
    this.email = email;
  }
}

Implementing Inheritance with Discriminators

Mongoose's Discriminator is the most powerful tool for handling model inheritance. It allows storing different types of documents in the same collection, distinguished by the __t field. This method is particularly suitable for polymorphic data scenarios.

Basic implementation example:

const eventSchema = new mongoose.Schema({
  time: Date,
  location: String
}, { discriminatorKey: 'kind' });

const Event = mongoose.model('Event', eventSchema);

// Create a Concert sub-model
const Concert = Event.discriminator('Concert', 
  new mongoose.Schema({
    artist: String,
    ticketPrice: Number
  })
);

// Create a Conference sub-model
const Conference = Event.discriminator('Conference',
  new mongoose.Schema({
    topic: String,
    attendees: Number
  })
);

Queries automatically handle type identification:

// Query all events
const events = await Event.find();

// Query only concerts
const concerts = await Concert.find();

Schema Extension Methods

Beyond inheritance, Mongoose also supports extending Schema functionality through the Plugin mechanism. This approach does not alter the inheritance chain but extends functionality horizontally.

Timestamp plugin example:

function timestampPlugin(schema) {
  schema.add({ 
    createdAt: Date,
    updatedAt: Date 
  });

  schema.pre('save', function(next) {
    const now = new Date();
    if (!this.createdAt) {
      this.createdAt = now;
    }
    this.updatedAt = now;
    next();
  });
}

// Apply the plugin
const productSchema = new mongoose.Schema({
  name: String,
  price: Number
});
productSchema.plugin(timestampPlugin);

Implementing Multi-Level Inheritance

Mongoose supports multi-level model inheritance, enabling the construction of complex inheritance hierarchies. This is common in enterprise applications, such as product categorization in e-commerce systems.

Three-level inheritance example:

// Base product model
const productSchema = new mongoose.Schema({
  sku: String,
  price: Number
});

// Electronic product inheriting from Product
const electronicSchema = new mongoose.Schema({
  warranty: Number
});

// Phone inheriting from Electronic
const phoneSchema = new mongoose.Schema({
  screenSize: Number,
  os: String
});

const Product = mongoose.model('Product', productSchema);
const Electronic = Product.discriminator('Electronic', electronicSchema);
const Phone = Electronic.discriminator('Phone', phoneSchema);

Queries can operate across levels:

// Query all products
const allProducts = await Product.find();

// Query only electronics
const electronics = await Electronic.find();

// Query specific phones
const phones = await Phone.find({ os: 'Android' });

Virtual Property Inheritance

Virtual properties behave uniquely in inheritance models—they are not automatically inherited by child models and require explicit handling.

Virtual property inheritance example:

const personSchema = new mongoose.Schema({
  firstName: String,
  lastName: String
});

personSchema.virtual('fullName').get(function() {
  return `${this.firstName} ${this.lastName}`;
});

const employeeSchema = new mongoose.Schema({
  department: String,
  position: String
});

// Inherit virtual property
employeeSchema.virtual('fullName').get(personSchema.virtuals.fullName.get);

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

Static and Instance Methods

Method inheritance is a key way to reuse functionality in models. Static methods operate on the model itself, while instance methods operate on document instances.

Method inheritance example:

const animalSchema = new mongoose.Schema({
  name: String,
  type: String
});

// Static method
animalSchema.statics.findByType = function(type) {
  return this.find({ type });
};

// Instance method
animalSchema.methods.speak = function() {
  console.log(`${this.name} makes a sound`);
};

const dogSchema = new mongoose.Schema({
  breed: String
});

// Inherit methods
Object.assign(dogSchema.statics, animalSchema.statics);
Object.assign(dogSchema.methods, animalSchema.methods);

const Animal = mongoose.model('Animal', animalSchema);
const Dog = Animal.discriminator('Dog', dogSchema);

Query Middleware Inheritance

Middleware behavior in inherited models requires special attention. Pre and post hooks are not automatically inherited and must be redefined in child models.

Middleware handling example:

const logSchema = new mongoose.Schema({
  message: String,
  level: String
});

logSchema.pre('save', function(next) {
  console.log('About to save log:', this.message);
  next();
});

const errorLogSchema = new mongoose.Schema({
  stack: String
});

// Inherit middleware
errorLogSchema.pre('save', logSchema.pre('save')[0]);

const Log = mongoose.model('Log', logSchema);
const ErrorLog = Log.discriminator('ErrorLog', errorLogSchema);

Index Inheritance Strategy

Indexes are not automatically inherited from parent to child models. They must be explicitly defined in each child model or handled using Schema merging techniques.

Index handling example:

const contentSchema = new mongoose.Schema({
  title: { type: String, index: true },
  body: String
});

const articleSchema = new mongoose.Schema({
  author: { type: String, index: true },
  tags: [String]
});

// Merge Schemas while preserving indexes
const mergedSchema = contentSchema.clone();
mergedSchema.add(articleSchema);

const Article = mongoose.model('Article', mergedSchema);

Multi-Database Inheritance

In cross-database scenarios, model inheritance requires special handling. Mongoose allows specifying different database connections via the connection option.

Multi-database inheritance example:

const mainDB = mongoose.createConnection('mongodb://localhost/main');
const analyticsDB = mongoose.createConnection('mongodb://localhost/analytics');

const userSchema = new mongoose.Schema({
  name: String,
  email: String
});

// Main database user model
const User = mainDB.model('User', userSchema);

// Analytics database inheriting user model
const AnalyticsUser = analyticsDB.model('User', userSchema);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.