Computed property based on relationship

I’m doing an online course platform, I have the users table andlessons`

I need to check when a user attended a class, so I made a belongsToMany relationship and I have the lesson_views table that holds the user id and the class id he attended

so in the listing, in Lesson’s model I’d like to do something like

static get computed() {
  return ['viewed']
}

getViewed({id, auth}) {
  const viewed = await Database.table('lesson_views')
  .where({ lesosn_id: id, user_id: auth.user.id })

  return (viewed) ? true : false
}

but I know it is not possible to query auth within a Model.

So how in the lesson listing can I tell which lesson the user has already attended?

1 Like

As getters are synchronous, you cannot run asynchronous code inside them (for asynchronous functionality, use hooks).

The same applies for Computed Properties as are Getters.

So how in the lesson listing can I tell which lesson the user has already attended?

You will have to use the power of Lucid!!
I imagine something similar to this as this is a many to many relationship:

User.js

lessons(){
 return belongsToMany(
   'App/Models/Lesson'
)
.pivotTable('users_lessons')
.withPivot(['attended'])
}

UserController.js

const user = await auth.getUser()
const hasAttendedTo = await user.lessons().wherePivot('attended',true).fetch()
1 Like

@jorgeyoma

on hooks I can’t use auth.user either.

Also, I do not want to list the lessons that the user attended.

I want to list ALL the lessons and mark the lessons you have already attended.

@yungsilva We disallow async computed properties on purpose, since you can fall into the trap, where you are running n+1 queries without realizing it. For example:

If you fetch 10 lessons and viewed property on each lesson is computed by making a database request, then basically you are making one request per lesson.

Regarding auth.user access inside the model is something we understand is painful right now and we will allow ways to share the controller context with models in upcoming version.

Unfortunately, there is no simple answer as of now, the best I can suggest is the the following code.

/**
 * Fetch lessons as usual
 */
const lessons = await Lesson.all()

/**
 * Fetch all views for the loaded lessons for that given user in a single
 * query.
 */
const lessonViews = await Database.table('lesson_views')
  .whereIn('lesosn_id', lessons.rows.map(({ id }) => id))
  .where('user_id', auth.user.id)

/**
 * Set `lessonViews` as a sideloaded property on the lesson model, so that
 * we can access them inside the computed property
 */
lessons.rows.forEach((lession) => {
  lession.$sideLoaded.lessonsViews = lessonViews
})

/**
 * Compute from the $sideLoaded array vs making a new database
 * call everytime. ON THE MODEL
 */
getViewed () {
  return !!this.$sideLoaded.lessonsViews.find((view) => view.lesosn_id === this.id)
}
1 Like

@virk

It works, thanks for answering.

but with each lesson in the final lesson array, it’s coming __meta__ with relationships, this is making json very big …

how do i just get viewed true or false