Nest.js 中的猫鼬子文档 [英] Mongoose Subdocuments in Nest.js

查看:78
本文介绍了Nest.js 中的猫鼬子文档的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将我的应用程序从 express.js 转移到 Nest.js,我找不到一种方法可以在另一个中引用一个 mongoose Schema,而不使用使用 mongoose.Schema({...}).

I'm moving my app from express.js to Nest.js, and I can't find a way to reference one mongoose Schema in another, without using old way of declaring Schema with mongoose.Schema({...}).

让我们使用文档中的示例,以便我可以澄清我的问题:

Let's use example from docs, so I can clarify my problem:

@Schema()
  export class Cat extends Document {
  @Prop()
  name: string;
}

export const CatSchema = SchemaFactory.createForClass(Cat);

现在,我想要的是这样的:

Now, what I want is something like this:

@Schema()
export class Owner extends Document {
  @Prop({type: [Cat], required: true})
  cats: Cat[];
}

export const OwnerSchema = SchemaFactory.createForClass(Owner);

当我以这种方式定义架构时,我会收到一个错误,类似于:无效的架构配置:Cat is not a valid在数组中输入 cats

When I define schemas this way I'd get an error, something like this: Invalid schema configuration: Cat is not a valid type within the array cats

那么,使用这种更面向对象的方法来定义架构,在另一个架构中引用一个架构的正确方法是什么?

So, what is the proper way for referencing one Schema inside another, using this more OO approach for defining Schemas?

推荐答案

我深入研究了源代码,了解了如何通过 SchemaFactory.createForClass 方法转换 Schema 类.

I dug into the source code and learned how Schema class is converted by the SchemaFactory.createForClass method.

@Schema()
export class Cat extends Document {
  @Prop()
  name: string;
}
export const catSchema = SchemaFactory.createForClass(Cat);

基本上,当你执行 SchemaFactory.createForClass(Cat)

Nest 会将 class 语法转换成 Mongoose schema 语法,所以最终转换的结果是这样的:

Nest will convert the class syntax into the Mongoose schema syntax, so in the end, the result of the conversion would be like this:

const schema = new mongoose.Schema({
    name: { type: String } // Notice that `String` is now uppercase.
});

2.转换是如何工作的?

看看这个文件:mongoose/prop.decorator.ts at master · nestjs/mongoose · GitHub

export function Prop(options?: PropOptions): PropertyDecorator {
  return (target: object, propertyKey: string | symbol) => {
    options = (options || {}) as mongoose.SchemaTypeOpts<unknown>;

    const isRawDefinition = options[RAW_OBJECT_DEFINITION];
    if (!options.type && !Array.isArray(options) && !isRawDefinition) {
      const type = Reflect.getMetadata(TYPE_METADATA_KEY, target, propertyKey);

      if (type === Array) {
        options.type = [];
      } else if (type && type !== Object) {
        options.type = type;
      }
    }

    TypeMetadataStorage.addPropertyMetadata({
      target: target.constructor,
      propertyKey: propertyKey as string,
      options,
    });
  };
}

在这里您可以看到 Prop() 装饰器在幕后做了什么.当你这样做时:

Here you could see what the Prop() decorator does behind the scene. When you do:

@Prop()
name: string;

Prop 函数将被调用,在这种情况下没有参数.

Prop function would be called, in this case with no arguments.

const type = Reflect.getMetadata(TYPE_METADATA_KEY, target, propertyKey);

使用Reflect API,我们可以获取您在执行name: string 时使用的数据类型.type 变量的值现在设置为 String.请注意,它不是 stringReflect API 将始终返回数据类型的构造函数版本,因此:

Using the Reflect API, we can get the data type that you use when you do name: string. The value of type variable is now set to String. Notice that it’s not string, the Reflect API will always return the constructor version of the data type so:

  • number 将被序列化为 Number
  • string 将被序列化为 String
  • boolean 将被序列化为 Boolean
  • 等等
  • number will be serialized as Number
  • string will be serialized as String
  • boolean will be serialized as Boolean
  • and so on

TypeMetadataStorage.addPropertyMetadata 然后将下面的对象存储到存储中.

TypeMetadataStorage.addPropertyMetadata will then store the object below into the store.

{
    target: User,
    propertyKey: ‘name’,
    options: { type: String }
}

我们来看看:mongoose/type-metadata.storage.ts at master · nestjs/mongoose · GitHub

export class TypeMetadataStorageHost {
  private schemas = new Array<SchemaMetadata>();
  private properties = new Array<PropertyMetadata>();

  addPropertyMetadata(metadata: PropertyMetadata) {
    this.properties.push(metadata);
  }
}

所以基本上该对象将被存储到 TypeMetadataStorageHost 中的 properties 变量中.TypeMetadataStorageHost 是一个将存储大量这些对象的单例.

So basically that object will be stored into the properties variable in TypeMetadataStorageHost. TypeMetadataStorageHost is a singleton that will store a lot of these objects.

要了解 SchemaFactory.createForClass(Cat) 如何生成 Mongoose 模式,请查看以下内容:mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub

To understand how the SchemaFactory.createForClass(Cat) produce the Mongoose schema, take a look at this: mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub

export class SchemaFactory {
  static createForClass(target: Type<unknown>) {
    const schemaDefinition = DefinitionsFactory.createForClass(target);
    const schemaMetadata = TypeMetadataStorage.getSchemaMetadataByTarget(
      target,
    );
    return new mongoose.Schema(
      schemaDefinition,
      schemaMetadata && schemaMetadata.options,
    );
  }
}

最重要的部分是:const schemaDefinition = DefinitionsFactory.createForClass(target);.请注意,这里的目标是您的 Cat 类.

The most important part is: const schemaDefinition = DefinitionsFactory.createForClass(target);. Notice that the target here is your Cat class.

你可以在这里看到方法定义:mongoose/在 master · nestjs/mongoose · GitHub 上的定义.factory.ts

You could see the method definition here: mongoose/definitions.factory.ts at master · nestjs/mongoose · GitHub

export class DefinitionsFactory {
  static createForClass(target: Type<unknown>): mongoose.SchemaDefinition {
    let schemaDefinition: mongoose.SchemaDefinition = {};

  schemaMetadata.properties?.forEach((item) => {
    const options = this.inspectTypeDefinition(item.options as any);
    schemaDefinition = {
    [item.propertyKey]: options as any,
      …schemaDefinition,
    };
  });

    return schemaDefinition;
}

schemaMetadata.properties 包含您在执行TypeMetadataStorage.addPropertyMetadata 时存储的对象:

schemaMetadata.properties contains the object that you stored when you did TypeMetadataStorage.addPropertyMetadata:

[
    {
        target: User,
        propertyKey: ‘name’,
        options: { type: String }
    }
]

forEach 将产生:

{
    name: { type: String }
}

最后,它将被用作 mongoose.Schema 构造函数的参数 mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub:

In the end, it will be used as the argument to the mongoose.Schema constructor mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub:

return new mongoose.Schema(
    schemaDefinition,
    schemaMetadata && schemaMetadata.options,
);

4.所以来回答这个问题:

你应该把什么作为 Prop() 参数?

还记得 Nest 什么时候使用 forEach 来生成 Mongoose Schema 吗?

Remember when Nest does the forEach to generate the Mongoose Schema?

schemaMetadata.properties?.forEach((item) => {
  const options = this.inspectTypeDefinition(item.options as any);
  schemaDefinition = {
    [item.propertyKey]: options as any,
    …schemaDefinition,
  };
});

为了获得 options,它使用 inspectTypeDefinition 方法.你可以看到下面的定义:

To get the options it uses inspectTypeDefinition method. You could see the definition below:

private static inspectTypeDefinition(options: mongoose.SchemaTypeOpts<unknown> | Function): PropOptions {
  if (typeof options === 'function') {
    if (this.isPrimitive(options)) {
      return options;
    } else if (this.isMongooseSchemaType(options)) {
      return options;
    }
    return this.createForClass(options as Type<unknown>);   
  } else if (typeof options.type === 'function') {
    options.type = this.inspectTypeDefinition(options.type);
    return options;
  } else if (Array.isArray(options)) {
    return options.length > 0
      ? [this.inspectTypeDefinition(options[0])]
      : options;
  }
  return options;
}

您可以得出以下结论:

  1. 如果optionsfunction,例如StringSchemaType,它将直接返回并使用作为猫鼬选项.
  2. 如果 options 是一个 Array,它将返回该数组的第一个索引并将其包装在一个数组中.
  3. 如果 options 不是 Arrayfunction,例如,如果它只是一个普通的 object{ type: String, required: true },会直接返回,作为Mongoose的选项使用.
  1. If the options is a function such as String or a SchemaType it will be returned directly and used as the Mongoose options.
  2. If the options is an Array, it will return the first index of that array and wrap it in an array.
  3. If the options is not an Array or function, for example, if it’s only a plain object such as { type: String, required: true }, it will be returned directly and used as the Mongoose options.

答案

所以要添加一个从 CatOwner 的引用,你可以这样做:

Answer

So to add a reference from Cat to Owner, you could do:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Schema as MongooseSchema } from 'mongoose';
import { Owner } from './owner.schema.ts';

@Schema()
export class Cat extends Document {
  @Prop()
  name: string;

  @Prop({ type: MongooseSchema.Types.ObjectId, ref: Owner.name })
  owner: Owner;
}

export const catSchema = SchemaFactory.createForClass(Cat);

关于如何添加一个从OwnerCat的引用,我们可以这样做:

As for how to add a reference from Owner to Cat, we could do:

@Prop([{ type: MongooseSchema.Types.ObjectId, ref: Cat.name }])

更新

在评论部分回答关于以下问题:

Update

To answer the question in the comment section about:

如果你正确阅读了答案,你应该有足够的知识来做到这一点.但如果你没有,这里是 TLDR 答案.

If you read the answer properly, you should have enough knowledge to do this. But if you didn't, here's the TLDR answer.

请注意,我强烈建议您在前往此处之前阅读整个答案.

Note that I strongly recommend you to read the entire answer before you go here.

image-variant.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';

@Schema()
export class ImageVariant {
  @Prop()
  url: string;

  @Prop()
  width: number;

  @Prop()
  height: number;

  @Prop()
  size: number;
}

export const imageVariantSchema = SchemaFactory.createForClass(ImageVariant);

image.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { imageVariantSchema, ImageVariant } from './imagevariant.schema';

@Schema()
export class Image extends Document {
  @Prop({ type: imageVariantSchema })
  large: ImageVariant;

  @Prop({ type: imageVariantSchema })
  medium: ImageVariant;

  @Prop({ type: imageVariantSchema })
  small: ImageVariant;
}

export const imageSchema = SchemaFactory.createForClass(Image);

这篇关于Nest.js 中的猫鼬子文档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆