Skip to content

Commit

Permalink
fix(core): propagate to owning side of 1:1 relation even if not initi…
Browse files Browse the repository at this point in the history
…alized

Closes #4879
  • Loading branch information
B4nan committed Oct 31, 2023
1 parent 70b75a5 commit 9b2c9fe
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/core/src/entity/EntityHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class EntityHelper {
inverse.add(owner);
}

if (prop.reference === ReferenceType.ONE_TO_ONE && entity && helper(entity).__initialized) {
if (prop.reference === ReferenceType.ONE_TO_ONE && entity && (!prop.owner || helper(entity).__initialized)) {
if (
(value != null && Reference.unwrapReference(inverse) !== owner) ||
(value == null && entity[prop2.name] != null)
Expand Down
191 changes: 191 additions & 0 deletions tests/features/unit-of-work/GH4879.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { BigIntType, EntitySchema, ref, Ref, wrap } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';

type ProfileProps = {
imageUrl: string;
active?: boolean;
id?: number;
userOrId?: User | number;
};

class Profile {

readonly id: number;
readonly imageUrl: string;
readonly active: boolean;
readonly user?: Ref<User>;

constructor(props: ProfileProps) {
this.imageUrl = props.imageUrl;
this.active = props.active ?? true;
this.id = props.id ?? 1;
this.user = ref(User, props.userOrId);
}

}

type CreateUserProps = {
firstName: string;
lastName: string;
email: string;
createdAt: Date;
updatedAt: Date;
profile?: Ref<Profile>;
};

class User {

readonly id!: number;
readonly firstName!: string;
readonly lastName!: string;
readonly email!: string;
readonly createdAt!: Date;
readonly updatedAt!: Date;

profile?: Ref<Profile>;

constructor(props: CreateUserProps) {
wrap<User>(this).assign(props);
}

}

const profileSchema = new EntitySchema({
class: Profile,
forceConstructor: true,
properties: {
id: {
type: BigIntType,
primary: true,
autoincrement: true,
},
imageUrl: {
type: String,
},
active: {
type: Boolean,
default: true,
},
user: {
entity: () => User,
reference: '1:1',
mappedBy: 'profile',
ref: true,
nullable: true,
},
},
});

const userSchema = new EntitySchema<User>({
class: User,
forceConstructor: true,
properties: {
id: {
type: BigIntType,
primary: true,
autoincrement: true,
},
firstName: {
type: 'string',
},
lastName: {
type: 'string',
},
email: {
type: 'string',
},
createdAt: {
type: 'timestamp',
onCreate: () => new Date(),
},
updatedAt: {
type: 'timestamp',
onCreate: () => new Date(),
onUpdate: () => new Date(),
},
profile: {
entity: () => Profile,
reference: '1:1',
inversedBy: 'user',
nullable: true,
ref: true,
},
},
});

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
dbName: ':memory:',
entities: [userSchema, profileSchema],
});
await orm.schema.createSchema();
});

afterEach(async () => {
await orm.schema.clearDatabase();
});

afterAll(async () => {
await orm.close();
});

test('creates a user and assign a profile to it (using entity)', async () => {
// Arrange
const aUser = orm.em.create(User, {
firstName: 'firstName',
lastName: 'lastName',
email: 'email@mail.com',
createdAt: new Date(),
updatedAt: new Date(),
});
await orm.em.flush();
orm.em.clear();

const em = orm.em.fork();

const aProfile = new Profile({
imageUrl: 'https://example.com',
userOrId: aUser,
});

// Act
await em.persistAndFlush(aProfile);

// Assert
const userWithProfile = await em.findOneOrFail(User, { id: aUser.id }, {
populate: ['profile'],
refresh: true,
});
expect(userWithProfile.profile).toBeTruthy();
});

test('creates a user and assign a profile to it (using id)', async () => {
// Arrange
const aUser = orm.em.create(User, {
firstName: 'firstName',
lastName: 'lastName',
email: 'email@mail.com',
createdAt: new Date(),
updatedAt: new Date(),
});
await orm.em.flush();
orm.em.clear();
const em = orm.em.fork();

const aProfile = new Profile({
imageUrl: 'https://example.com',
userOrId: aUser.id,
});

// Act
await em.persistAndFlush(aProfile);

// Assert
const userWithProfile = await em.findOneOrFail(User, { id: aUser.id }, {
populate: ['profile'],
refresh: true,
});
expect(userWithProfile.profile).toBeTruthy();
});

0 comments on commit 9b2c9fe

Please sign in to comment.