Skip to content

Commit

Permalink
test: add tests for self relationships (#10071)
Browse files Browse the repository at this point in the history
Initially I thought that we will have to add special checks to allow relationships referencing itself. However, it turned out that not to be the case. Instead, it had more to do with how self relationships are defined in Mikro ORM.

In case of `belongsTo` relationship we have to define the other side as well either as a `hasMany` or `hasOne`. For example:

**❌ The following code will fail, because no children are defined for the parent**
```ts
const user = model.define("user", {
  id: model.number(),
  username: model.text(),
  parent: model.belongsTo(() => user)
})
```

**✅ Addition of children relationship will make things work**
```ts
const user = model.define("user", {
  id: model.number(),
  username: model.text(),
  parent: model.belongsTo(() => user, { mappedBy: "children" }),
  children: model.hasMany(() => user, { mappedBy: "parent" }),
})
```

We can see the similar setup here with our `ProductCategory` MikroORM entity. https://github.com/medusajs/medusa/blob/develop/packages/modules/product/src/models/product-category.ts#L87-L94

@adrien2p Correct me if I am wrong. But I have added the tests for now so that we know the behavior of self relationships
  • Loading branch information
thetutlage authored Nov 13, 2024
1 parent 0ea5765 commit 98bf8c9
Showing 1 changed file with 230 additions and 0 deletions.
230 changes: 230 additions & 0 deletions packages/core/utils/src/dml/__tests__/entity-builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4702,6 +4702,236 @@ describe("Entity builder", () => {
},
})
})

test("define entity with relationship to itself via hasMany", () => {
const user = model.define("user", {
id: model.number(),
username: model.text(),
parent: model.belongsTo(() => user, { mappedBy: "children" }),
children: model.hasMany(() => user, { mappedBy: "parent" }),
})

const [User] = toMikroOrmEntities([user])

expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
deleted_at: Date | null
parent: {
id: number
username: string
deleted_at: Date | null
}
children: {
id: number
username: string
deleted_at: Date | null
}[]
}>()

const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
columnType: "integer",
name: "id",
fieldName: "id",
nullable: false,
getter: false,
setter: false,
},
username: {
reference: "scalar",
type: "string",
columnType: "text",
name: "username",
fieldName: "username",
nullable: false,
getter: false,
setter: false,
},
parent: {
name: "parent",
reference: "m:1",
entity: "User",
persist: false,
nullable: false,
},
parent_id: {
name: "parent_id",
reference: "m:1",
entity: "User",
columnType: "text",
fieldName: "parent_id",
mapToPk: true,
nullable: false,
onDelete: undefined,
isForeignKey: true,
},
children: {
cascade: undefined,
entity: "User",
mappedBy: "parent",
name: "children",
orphanRemoval: true,
reference: "1:m",
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
fieldName: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
fieldName: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
fieldName: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
})

test("define relationship with self via hasOne", () => {
const user = model.define("user", {
id: model.number(),
username: model.text(),
parent: model.belongsTo(() => user, { mappedBy: "child" }),
child: model.hasOne(() => user, { mappedBy: "parent" }),
})

const [User] = toMikroOrmEntities([user])

expectTypeOf(new User()).toMatchTypeOf<{
id: number
username: string
deleted_at: Date | null
parent: {
id: number
username: string
deleted_at: Date | null
}
child: {
id: number
username: string
deleted_at: Date | null
}
}>()

const metaData = MetadataStorage.getMetadataFromDecorator(User)
expect(metaData.className).toEqual("User")
expect(metaData.path).toEqual("User")
expect(metaData.properties).toEqual({
id: {
reference: "scalar",
type: "number",
columnType: "integer",
name: "id",
fieldName: "id",
nullable: false,
getter: false,
setter: false,
},
username: {
reference: "scalar",
type: "string",
columnType: "text",
name: "username",
fieldName: "username",
nullable: false,
getter: false,
setter: false,
},
parent: {
name: "parent",
mappedBy: "child",
reference: "1:1",
entity: "User",
nullable: false,
onDelete: undefined,
owner: true,
},
parent_id: {
name: "parent_id",
type: "string",
columnType: "text",
isForeignKey: true,
persist: false,
reference: "scalar",
getter: false,
setter: false,
nullable: false,
},
child: {
cascade: undefined,
entity: "User",
mappedBy: "parent",
name: "child",
nullable: false,
reference: "1:1",
},
created_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "created_at",
fieldName: "created_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
updated_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "updated_at",
fieldName: "updated_at",
defaultRaw: "now()",
onCreate: expect.any(Function),
onUpdate: expect.any(Function),
nullable: false,
getter: false,
setter: false,
},
deleted_at: {
reference: "scalar",
type: "date",
columnType: "timestamptz",
name: "deleted_at",
fieldName: "deleted_at",
nullable: true,
getter: false,
setter: false,
},
})
})
})

describe("Entity builder | manyToMany", () => {
Expand Down

0 comments on commit 98bf8c9

Please sign in to comment.