javascripttypescriptormadonis.jslucid

Adonis JS v5 relationship missing model attribute


I am learning how to use the Adonis framework (v5) and as the tradition dictates it, I am creating a todo list api to test it out.

The problem I am having is regarding the relationship between the User and Todo entities.

Here are the two models:

// file: app/Models/Todo.ts

export default class Todo extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @belongsTo(() => User, {
    foreignKey: 'id',
  })
  public author: BelongsTo<typeof User>

  @column()
  public completed: boolean

  @column()
  public title: string

  @column()
  public description: string | null

  @column.dateTime({ autoCreate: true })
  public createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime
}
// file: app/Models/User.ts

export default class User extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  public username: string

  @column({ serializeAs: null })
  public password: string

  @hasMany(() => Todo, {
    foreignKey: 'author',
  })
  public todos: HasMany<typeof Todo>

  @column.dateTime({ autoCreate: true })
  public createdAt: DateTime

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

  @beforeSave()
  public static async hashPassword(user: User) {
    if (user.$dirty.password) {
      // Only hash password if required
      user.password = await Hash.make(user.password)
    }
  }
}

I did not include the migrations files but I will edit this question if they are needed.

What I am expecting is that I should be able to create and save Users into my database, the same for Todo entries and link the todo entries to their author. The documentation provides us with an example but I don't see where I am doing something wrong.

So to test it out I am using the node ace repl command as follow:

// log of running the commands in the AdonisJS v5 REPL
> loadModels()
recursively reading models from "app/Models"
Loaded models module. You can access it using the "models" variable
> undefined
> const testUser = await models.User.create({ username: 'testUser', password: 'password' })
undefined
> await testUser.related('todos').create({ title: 'Example todo entry' })
Uncaught:
Exception: E_MISSING_MODEL_ATTRIBUTE: "User.todos" expects "author" to exist on "Todo" model, but is missing
    at <my-app-directory>\REPL23:1:39
    at Proxy.related (<my-app-directory>\node_modules\@adonisjs\lucid\build\src\Orm\BaseModel\index.js:1436:18)
    at HasMany.boot (<my-app-directory>\node_modules\@adonisjs\lucid\build\src\Orm\Relations\HasMany\index.js:74:12)
    at KeysExtractor.extract (<my-app-directory>\node_modules\@adonisjs\lucid\build\src\Orm\Relations\KeysExtractor.js:28:39)
    at Array.reduce (<anonymous>)
    at <my-app-directory>\node_modules\@adonisjs\lucid\build\src\Orm\Relations\KeysExtractor.js:32:23
>

I don't understand the error message since author actually exists on the Todo model.

How can I solve this problem and get my todo app up and running?

Thank you by advance!


Solution

  • The error is that you are missing a field inside your model.

    You need to define all fields with the @column() decorator. In your example, you are not defining the column author.

    When creating a relation, you must have one column (the FK) and one relation.

    If we assume that you have a column user_id inside your todos table, then you need to add the column user_id inside your Todo model.

    Here is a correct example:

    class User extends BaseModel {
      // ...
      @hasMany(() => Todo)
      todos: HasMany<typeof Todo>
    }
    
    class Todo extends BaseModel {
      @column()
      user_id: number
    
      @belongsTo(() => User)
      author: BelongsTo<typeof User>
    }