#4090·mikro-orm

Cannot populate @OneToMany when using Embeddables

Author: pantajoeCreated Mar 3, 2023Updated Mar 3, 2023
Labelsenhancement

Describe the bug

Hey there! First of all, thanks a lot for this very stable and intuitive TS ORM! ❤️

Since a couple of versions, an @Embeddable can have a @ManyToOne relationship through its parent. This works fine.

However, the inverse side that would use a @OneToMany collection cannot populate the user.

Stack trace

No stack trace since no error.

To Reproduce

Here is an example setup:

typescript
@Embeddable()
export class Address {
  constructor(street: string, city: string, country: Country) {
    this.street = street
    this.city = city
    this.country = country
  }

  @Property()
  street!: string

  @Property()
  city!: string

  @ManyToOne()
  country!: Country
}

@Entity()
export class Country {
  @PrimaryKey({ autoincrement: true })
  id!: number

  @Property()
  name!: string

  @OneToMany(() => User, (user) => (user as any).address_country)
  users = new Collection<User>(this)
}

@Entity()
export class User {
  @PrimaryKey({ autoincrement: true })
  id!: number

  @Property()
  name!: string

  @Embedded(() => Address)
  address!: Address
}

You can populate the country property from the side of the User, but you cannot populate the users collection from the Country's side.

Expected behavior

One should be able to populate the users collection also from the Country's side.

Additional context Here is a test suite that explains everything in detail:

typescript
import { MikroORM } from '@mikro-orm/core'
import { SqliteDriver } from '@mikro-orm/sqlite'
import { Address } from './address'
import { Country } from './country'
import { User } from './user'

describe('Issue', () => {
  let orm: MikroORM

  beforeAll(async () => {
    orm = await MikroORM.init({
      entities: [User, Country, Address],
      dbName: ':memory:',
      driver: SqliteDriver,
      allowGlobalContext: true,
    })
    await orm.schema.createSchema()
  })

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

  afterAll(() => orm.close(true))

  beforeEach(async () => {
    const germany = orm.em.create(Country, { name: 'Germany' })
    orm.em.create(User, { name: 'John Doe', address: new Address('Main Street', 'Berlin', germany) })
    await orm.em.flush()
  })

  test('the country can be populated through the address of a user', async () => {
    const user = await orm.em.findOneOrFail(User, { name: 'John Doe' }, { populate: ['address.country'] })
    expect(user.address.country.name).toBe('Germany')
  })

  test('the users can be populated through the address of a country', async () => {
    const country = await orm.em.findOneOrFail(Country, { name: 'Germany' }, { populate: ['users'] })
    expect(country.users[0].name).toBe('John Doe') // ❌ this one fails
  })

  test('can init the user collection of a country', async () => {
    const country = await orm.em.findOneOrFail(Country, { name: 'Germany' })
    await country.users.init()
    expect(country.users.isInitialized()).toBe(true)
    expect(country.users[0].name).toBe('John Doe')
  })
})

Versions

Dependency Version
node 16.15.1
typescript 4.9.5
mikro-orm 5.6.13
sqlite3 5.1.4