Nest.js中的猫鼬子文档

时间:2020-07-02 20:31:32

标签: reference schema nestjs subdocument

我正在将我的应用程序从express.js移到Nest.js,并且我找不到一种在另一种引用猫鼬模式的方法,而没有使用通过mongoose.Schema({... }。

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

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

export const CatSchema = SchemaFactory.createForClass(Cat);

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

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

export const OwnerSchema = SchemaFactory.createForClass(Owner);

当我以这种方式定义架构时,会出现以下错误:无效的架构配置:Cat无效 在数组cats

中输入

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

2 个答案:

答案 0 :(得分:29)

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

那么它如何工作?

1。在下面看这个例子:

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

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

Nest会将类语法转换为Mongoose模式语法,因此最后,转换结果将如下所示:

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()装饰器在后台执行的操作。 当您这样做时:

@Prop()
name: string;

Prop函数将被调用,在这种情况下将不带参数。

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

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

  • number将序列化为Number
  • string将被序列化为String
  • boolean将序列化为Boolean
  • 依此类推

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

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

因此基本上,该对象将存储在properties中的TypeMetadataStorageHost变量中。 TypeMetadataStorageHost是一个单例,将存储很多这些对象。

3。模式生成

要了解SchemaFactory.createForClass(Cat)如何产生猫鼬模式,请查看以下内容: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类。

您可以在此处看到方法定义: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时存储的对象:

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

forEach将产生:

{
    name: { type: String }
}

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

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

4。所以要回答这个问题:

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

还记得Nest何时forEach生成猫鼬模式吗?

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

要获取options,它使用inspectTypeDefinition方法。您可以看到以下定义:

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. 如果optionsArray,它将返回该数组的第一个索引并将其包装在数组中。
  3. 例如,如果options不是Arrayfunction,如果它只是普通的object,例如{ type: String, required: true },则将返回直接用作猫鼬选项。

答案

因此,要将引用从Cat添加到Owner,可以执行以下操作:

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添加引用,我们可以这样做:

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

更新

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

如何将模式嵌入另一个模式?

如果您正确阅读了答案,那么您应该具有足够的知识来做到这一点。但是,如果您不这样做,这就是TLDR的答案。

请注意,我强烈建议您在进入此处之前先阅读整个答案。

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

答案 1 :(得分:0)

import { Prop, raw, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { Education } from '../../education/schemas';
import { RECORD_STATUS } from '../../common/common.constants';
import { Employment } from '../../employment/schemas';
import {
    JOB_SEARCH_STATUS,
    LANGUAGE_PROFICIENCY
} from '../user-profile.constants';

const externalLinks = {
    linkedInUrl: { type: String },
    githubUrl: { type: String },
    twitterUrl: { type: String },
    blogUrl: { type: String },
    websiteUrl: { type: String },
    stackoverflowUrl: { type: String }
};

const address = {
    line1: { type: String, required: true },
    line2: { type: String },
    zipCode: { type: String },
    cityId: { type: Number },
    countryId: { type: Number }
};

const language = {
    name: { type: String, require: true },
    code: { type: String, required: true },
    proficiency: { type: String, required: true, enum: LANGUAGE_PROFICIENCY }
};

const options = {
    timestamps: true,
};

export type UserProfileDocument = UserProfile & mongoose.Document;

@Schema(options)
export class UserProfile {

    _id: string;

    @Prop()
    firstName: string;

    @Prop()
    lastName: string;

    @Prop()
    headline: string;

    @Prop({
        unique: true,
        trim: true,
        lowercase: true
    })
    email: string;

    @Prop()
    phoneNumber: string

    @Prop(raw({
        jobSearchStatus: { type: String, enum: JOB_SEARCH_STATUS, required: true }
    }))
    jobPreferences: Record<string, any>;

    @Prop(raw(externalLinks))
    externalLinks: Record<string, any>;

    @Prop([String])
    skills: string[];

    @Prop(raw({ type: address, required: false }))
    address: Record<string, any>;

    @Prop()
    birthDate: Date;

    @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Employment' }] })
    employments: Employment[];

    @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Education' }] })
    educations: Education[];

    @Prop(raw([language]))
    languages: Record<string, any>[];

    @Prop()
    timeZone: string;

    @Prop()
    createdAt: Date;

    @Prop()
    updatedAt: Date;

    @Prop({
        enum: RECORD_STATUS,
        required: true,
        default: RECORD_STATUS.Active
    })
    recordStatus: string;
}

export const UserProfileSchema = SchemaFactory.createForClass(UserProfile);