Fix(dml): DML default/nullable management (#10363)

RESOLVES FRMW-2813

**What**
Extract what we have done in the order module DML migration.
This includes all the changes we discussed around default value and nullable management

From now on, when the default is being set through the DML on a property, no default initializer will be created, this means that when a mikro orm class is being instantiated, no default will be applied at instantiation time. This fixes the issue when retrieving data and not selecting fields that have a default. The default initializer was taking the relay making you think the data was present except that it was the value of the default initializer. From now on, the default value is applied as follows
- db column default
- before flush
- on persist
The same applies for nullable properties/columns

Co-authored-by: Harminder Virk <1706381+thetutlage@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2024-11-29 12:46:26 +01:00
committed by GitHub
parent 194361b95a
commit ed11834d6e
3 changed files with 176 additions and 13 deletions

View File

@@ -755,10 +755,10 @@ describe("Entity builder", () => {
const userInstance = new User()
expect(userInstance.username).toEqual(null)
expect(userInstance.username).toEqual(undefined)
expect(userInstance.spend_limit).toEqual(undefined)
expect(userInstance.raw_spend_limit).toEqual(null)
expect(userInstance.raw_spend_limit).toEqual(undefined)
userInstance.username = "john"
expect(userInstance.username).toEqual("john")
@@ -1121,7 +1121,7 @@ describe("Entity builder", () => {
const metaData = MetadataStorage.getMetadataFromDecorator(User)
const userInstance = new User()
expect(userInstance.role).toEqual(null)
expect(userInstance.role).toEqual(undefined)
userInstance.role = "admin"
expect(userInstance.role).toEqual("admin")
@@ -1578,7 +1578,10 @@ describe("Entity builder", () => {
expect(metaData.path).toEqual("User")
expect(metaData.hooks).toEqual({
beforeCreate: ["generateId"],
beforeCreate: [
"generateId",
"deleted_at_setDefaultValueOnBeforeCreate",
],
onInit: ["generateId"],
})
@@ -1685,7 +1688,10 @@ describe("Entity builder", () => {
expect(metaData.path).toEqual("User")
expect(metaData.hooks).toEqual({
beforeCreate: ["generateId"],
beforeCreate: [
"generateId",
"deleted_at_setDefaultValueOnBeforeCreate",
],
onInit: ["generateId"],
})
@@ -1793,7 +1799,10 @@ describe("Entity builder", () => {
expect(metaData.path).toEqual("User")
expect(metaData.hooks).toEqual({
beforeCreate: ["generateId"],
beforeCreate: [
"generateId",
"deleted_at_setDefaultValueOnBeforeCreate",
],
onInit: ["generateId"],
})

View File

@@ -119,13 +119,20 @@ export function defineProperty(
/**
* Here we initialize nullable properties with a null value
*/
if (field.nullable) {
Object.defineProperty(MikroORMEntity.prototype, field.fieldName, {
value: null,
configurable: true,
enumerable: true,
writable: true,
})
if (isDefined(field.defaultValue) || field.nullable) {
const defaultValueSetterHookName = `${field.fieldName}_setDefaultValueOnBeforeCreate`
MikroORMEntity.prototype[defaultValueSetterHookName] = function () {
if (isDefined(field.defaultValue) && this[propertyName] === undefined) {
this[propertyName] = field.defaultValue
return
}
if (field.nullable && this[propertyName] === undefined) {
this[propertyName] = null
return
}
}
BeforeCreate()(MikroORMEntity.prototype, defaultValueSetterHookName)
}
if (SPECIAL_PROPERTIES[field.fieldName]) {

View File

@@ -0,0 +1,147 @@
import { MetadataStorage, MikroORM } from "@mikro-orm/core"
import { model } from "../../entity-builder"
import {
mikroORMEntityBuilder,
toMikroOrmEntities,
} from "../../helpers/create-mikro-orm-entity"
import { createDatabase, dropDatabase } from "pg-god"
import { CustomTsMigrationGenerator, mikroOrmSerializer } from "../../../dal"
import { EntityConstructor } from "@medusajs/types"
import { pgGodCredentials } from "../utils"
import { FileSystem } from "../../../common"
import { join } from "path"
export const fileSystem = new FileSystem(
join(__dirname, "../../integration-tests-migrations-enum")
)
describe("EntityBuilder", () => {
const dbName = "EntityBuilder-default"
let orm!: MikroORM
let User: EntityConstructor<any>
afterAll(async () => {
await fileSystem.cleanup()
})
beforeEach(async () => {
MetadataStorage.clear()
mikroORMEntityBuilder.clear()
const user = model.define("user", {
id: model.id().primaryKey(),
username: model.text(),
points: model.number().default(0).nullable(),
})
;[User] = toMikroOrmEntities([user])
await createDatabase({ databaseName: dbName }, pgGodCredentials)
orm = await MikroORM.init({
entities: [User],
tsNode: true,
dbName,
password: pgGodCredentials.password,
host: pgGodCredentials.host,
user: pgGodCredentials.user,
type: "postgresql",
migrations: {
generator: CustomTsMigrationGenerator,
path: fileSystem.basePath,
},
})
const migrator = orm.getMigrator()
await migrator.createMigration()
await migrator.up()
})
afterEach(async () => {
await orm.close()
await dropDatabase(
{ databaseName: dbName, errorIfNonExist: false },
pgGodCredentials
)
})
it("set the points to default value before creating the record", async () => {
let manager = orm.em.fork()
const user1 = manager.create(User, {
username: "User 1",
})
expect(user1.points).toBe(undefined)
await manager.persistAndFlush([user1])
manager = orm.em.fork()
const user = await manager.findOne(User, {
id: user1.id,
})
expect(await mikroOrmSerializer(user)).toEqual({
id: user1.id,
username: "User 1",
points: 0,
created_at: expect.any(Date),
updated_at: expect.any(Date),
deleted_at: null,
})
})
it("set the points to null when explicitly set to null", async () => {
let manager = orm.em.fork()
const user1 = manager.create(User, {
username: "User 1",
points: null,
})
expect(user1.points).toBe(null)
await manager.persistAndFlush([user1])
manager = orm.em.fork()
const user = await manager.findOne(User, {
id: user1.id,
})
expect(await mikroOrmSerializer(user)).toEqual({
id: user1.id,
username: "User 1",
points: null,
created_at: expect.any(Date),
updated_at: expect.any(Date),
deleted_at: null,
})
})
it("set the points to null during updated", async () => {
let manager = orm.em.fork()
const user1 = manager.create(User, {
username: "User 1",
})
expect(user1.points).toBe(undefined)
await manager.persistAndFlush([user1])
manager = orm.em.fork()
user1.points = null
await manager.persistAndFlush([user1])
const user = await manager.findOne(User, {
id: user1.id,
})
expect(await mikroOrmSerializer(user)).toEqual({
id: user1.id,
username: "User 1",
points: null,
created_at: expect.any(Date),
updated_at: expect.any(Date),
deleted_at: null,
})
})
})