adonisjs/lucid

HasOne relation with Null Value

Opened this issue · 6 comments

Package version

18.4.0

Describe the bug

the Doc say :

Preload relationship
Preloading allows you to fetch the relationship data alongside the main query. For example: Select all the users and preload their profiles at the same time.

The preload method accepts the name of the relationship defined on the model.
The relationship property value for the hasOne and the belongsTo relationship is either set to the related model instance or null when no records are found. The relationship property value is an array of the related model instance for all other relationship types.

but, with hasone, the relation not work with null value. It's OK with relation BelongTo

Reproduction repo

https://github.com/adonisjs/lucid/blob/v18.4.0/src/Orm/Relations/HasOne/QueryBuilder.ts#L84

Hi, I'm a colleague of @sergefabre, I'll try to add some details about our problem. We're using the preload method on a query builder (Signalement).

let sql = Signalement.query().preload('anomalie', (ano) =>
  ano.select(['id', 'statut', 'commentaireCloture', 'timestampDerniereModif'])
)

Here is the queryBuilder code for hasOne relationship:
https://github.com/adonisjs/lucid/blob/v18.4.0/src/Orm/Relations/HasOne/QueryBuilder.ts#L84

And here the queryBuilder code for belongsTo relationship:
https://github.com/adonisjs/lucid/blob/v18.4.0/src/Orm/Relations/BelongsTo/QueryBuilder.ts#L86

It is clear that in the hasOne relationship, a value equal to null or undefined throws an exception, in the getValue() method as it uses ensureValue method:
https://github.com/adonisjs/lucid/blob/v18.4.0/src/utils/index.ts#L45C1-L53C2

export function ensureValue(collection: any, key: string, missingCallback: () => void) {
  const value = collection[key]
  if (value === undefined || value === null) {
    missingCallback()
    return
  }


  return value
}

Whereas the belongsTo relationship only checks for undefined values and allows null values.
https://github.com/adonisjs/lucid/blob/v18.4.0/src/Orm/Relations/BelongsTo/QueryBuilder.ts#L98C7-L105C11

const foreignKeyValues = this.parent
  .map((model) => model[this.relation.foreignKey])
  .filter((foreignKeyValue) => {
    if (foreignKeyValue === undefined) {
      this.raiseMissingForeignKey()
    }
    return foreignKeyValue !== null
  })

This is not what is mentioned in the docs : https://v5-docs.adonisjs.com/guides/models/relationships#preload-relationship

We found the related question in SO, applied the answer (using belongsTo relationship instead of hasOne) and now it works.
https://stackoverflow.com/questions/64094574/adonisjs-5-preload-model-even-if-foreign-key-is-null

@thetutlage what are your thoughts on this ? Is this by design ? Perhaps the docs should mention this explicitly.

Kind regards

Can share the relationships and the table schema for these relationships

image

Model Signalement

 @belongsTo(() => Anomalie, {
    foreignKey: 'anomalieId',
    localKey: 'id',
    onQuery: (ano) => {
      if (ano.client.dialect.name === 'postgres') ano.withSchema(Anomalie.schema)
    },
  })
  public anomalie: BelongsTo<typeof Anomalie>`

Model Anomalie :

  @column({ isPrimary: true }) public id: number
  @column.dateTime({ autoCreate: true }) public createdAt: DateTime
  @column.dateTime({ autoCreate: true, autoUpdate: true }) public updatedAt: DateTime

  @column() public statut: string
  @column() public commentaireCloture: string
  @column.dateTime() public timestampDerniereModif: DateTime

  @hasMany(() => Signalement, {
    localKey: 'id',
    foreignKey: 'anomalieId',
  })
  public signalements: HasMany<typeof Signalement>

anomalie_id of table "Signalements", is "NULLABLE"

hi guy, I had the same problem as you

hi
With this code, it works

`Vine.macro('defaultSetNull', addNullableToAny)
VineString.macro('defaultSetNull', addNullableToAny)
VineNumber.macro('defaultSetNull', addNullableToAny)
VineDate.macro('defaultSetNull', addNullableToAny)

/**

  • Informing TypeScript about the newly added method
    */
    declare module '@vinejs/vine' {
    interface Vine {
    defaultSetNull<A, B, C>(
    type: BaseLiteralType<A, B, C>
    ): NullableModifier<BaseLiteralType<A, B, C>>
    }

interface VineString {
defaultSetNull(): NullableModifier
}
interface VineNumber {
defaultSetNull(): NullableModifier
}

interface VineDate {
defaultSetNull(): NullableModifier
}`