node.js - Modeller refererar till varandra fel: problem med cirkulära beroenden


admin.model.ts

import mongoose, { Schema, Document } from 'mongoose';
import UserRole, { IUserRole } from './user-role.model';

export interface IAdmin extends Document {
    role: IUserRole;
}

let adminSchema = new Schema<IAdmin>({
    role: {
        type: Schema.Types.ObjectId, ref: UserRole
    }
});

export default mongoose.model<IAdmin>('Admin', adminSchema);

användarroll.model.ts

import { Schema, Document, model } from 'mongoose';

export interface IUserRole extends Document{
    updated_by: IAdmin|string;
}

let userRoleSchema = new Schema<IUserRole>({
    updated_by: {
        type: Schema.Types.ObjectId, ref: Admin
    }
})

export default model<IUserRole>('UserRole', userRoleSchema);
MongooseError: Invalid ref at path "updated_by". Got undefined
    at validateRef (/home/ess24/ess-smartlotto/node-rest/node_modules/mongoose/lib/helpers/populate/validateRef.js:17:9)
    at Schema.path (/home/ess24/ess-smartlotto/node-rest/node_modules/mongoose/lib/schema.js:655:5)
    at Schema.add (/home/ess24/ess-smartlotto/node-rest/node_modules/mongoose/lib/schema.js:535:14)
    at require (internal/modules/cjs/helpers.js:88:18)
[ERROR] 22:25:54 MongooseError: Invalid ref at path "updated_by". Got undefined

Här är mina två modeller, hur kan jag lösa denna typ av problem och hur hanterar man cirkulära beroenden?

Svar #1

Du måste sätta ref-värdena för UserRole och Admin i '' på detta sätt:

const adminSchema = new Schema<IAdmin>({
    role: {
        type: Schema.Types.ObjectId, ref: 'UserRole' // Name of the model you are referencing
    }
});
let userRoleSchema = new Schema<IUserRole>({
    updated_by: {
        type: Schema.Types.ObjectId, ref: 'Admin' // <- and here
    }
})

Svar #2

Medan svaret ovan av @gabriel-lupu är korrekt, verkar det i nyare versioner rekommenderas en annan lösning.

Fixa:

  1. lägga till citattecken: ref: 'UserRole' // Rekommenderad första åtgärd
  2. lägg till en pilfunktion: ref:() => UserRole

Typegoose-dokumentationen säger följande:

Alternativen ref och type kan båda också definieras utan() =>, men det rekommenderas generellt att använda det med. Om() => inte används kan det uppstå problem när klassen (/ variabeln) definieras efter dekoratorn som kräver den. Avsnitt i dokumentationen för referens.

På grund av att ordningen på klasser laddas och ordnas om vid körning, kan detta resultera i att vissa referenser är null/undefined/inte existerar. Detta är anledningen till att Typegoose tillhandahåller följande:

 class Nested {
    @prop()
    public someNestedProperty: string;
   }

  // Recommended first fix:
  class Main {
    @prop({ ref: () => Nested }) // since 7.1 arrow functions can be used to 
    defer getting the type
    public nested: Ref<Nested>;
  }

  // Not recommended workaround (hardcoding model name):
  class Main {
    @prop({ ref: 'Nested' }) // since 7.0 it is recommended to use "console.log(getName(Class))" to get the generated name once and hardcode it like shown here
    public nested: Ref<Nested>;
  }

Avsnitt av dokumentationen med exemplet ovan

Förklaring:

Felet MongooseError: Ogiltig referens vid sökvägen "updated_by". Fick undefined verkar bero på ett cirkulärt beroende.

  • IAdmin har en role-egenskap som refererar till IUserRole.
  • IUserRole har en updated_by-egenskap som refererar till IAdmin.
  1. När admin.model.ts laddas importerar det user-role.model.ts
  2. user-role.model.ts behöver IAdmin för updated_by-egenskapen från admin.model.ts
  3. admin.model.ts har inte laddats färdigt än vilket resulterar i att updated_by som refererar till det blir undefined.

Relevant förklaring från Mozilla om hur moduler hanterar cirkulära beroenden.

I ett cykliskt beroende hamnar du med en slinga i grafen. Vanligtvis är detta en lång slinga. Men för att förklara problemet kommer jag att använda ett påhittat exempel med en kort slinga.

Ett komplext moduldiagram med en cykel av 4 moduler till vänster. En enkel cykel av 2 moduler till höger.

Låt oss se hur detta skulle fungera med CommonJS-moduler. Först skulle huvudmodulen köra tills require-satsen. Sedan skulle den gå vidare för att ladda counter-modulen.

Ett commonJS-modul, med en variabel som exporteras från main.js efter ett require-uttalande till counter.js, som beror på den importen

Räknemodulen skulle då försöka få åtkomst till meddelandet från exportobjektet. Men eftersom detta ännu inte har utvärderats i huvudmodulen kommer detta att returnera undefined. JS-motorn kommer att tilldela utrymme i minnet för den lokala variabeln och sätta värdet till undefined.

Meddelandevariabeln kommer att initialiseras och läggas till i minnet. Men eftersom det inte finns någon koppling mellan de två, kommer den att förbli odefinierad i den nödvändiga modulen.

Om exporten hanterades med live-bindningar skulle räknarmodulen så småningom se det korrekta värdet. När timeouten körs skulle main.js:s utvärdering ha slutförts och fyllt i värdet. hur moduler importeras

Båda? lösningarna verkar skapa en direktbinding.

Closures kan också stänga över importerade värden, vilka betraktas som levande bindningar, eftersom när det ursprungliga värdet ändras, ändras det importerade värdet därefter.

// closureCreator.js
import { x } from "./myModule.js";

export const getX = () => x; // Close over an imported live binding

Relaterade artiklar