Skip to content

Cannot populate @OneToMany when using Embeddables #4090

@pantajoe

Description

@pantajoe

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:

@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:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions