Recursive Children of a Model

Hello All i just try adonisjs for two days, because it have a lot of similarities with laravel, i thought it will more easier to learn nodejs by using adonisjs.

i want to create a function that returning arrays of ids. i can do it in laravel but, after trying for a day, still make no progress… so i need you guys to help me.

i am currently using adonisjs v.4.1 and mysql 5.7
here is my table migration file :

class CreatePeopleSchema extends Schema {
  up() {
    this.create('people', (table) => {
      table.increments('id')
      table.string('name', 100).notNullable()
      table.string('code', 100).notNullable().unique()
      table.integer('parent_id').unsigned().nullable()
      table.boolean('status').notNullable().defaultTo(true)
      table.enum('role', ['TELESPV', 'TELEADMIN', 'TELESALES', 'SNCSSPV', 'CSSPV', 'CS']).defaultTo('TELESALES').notNullable()
      table.date('join_date').nullable()
      table.timestamps()
      table.index('code')
      table.index('status')
      table.index('role')
      table.foreign('parent_id').references('people.id')
    })
  }

  down() {
    this.drop('people')
  }
}

module.exports = CreatePeopleSchema

and here is my people model :

class People extends Model {

  static get table() {
    return 'people'
  }

  static get primaryKey() {
    return 'id'
  }

  static get hidden() {
    return ['created_at', 'updated_at']
  }

  children() {
    return this.hasMany('App/Models/People', 'id', 'parent_id')
  }

}

and i have this 2 functions in my controller :

constructor() {
    this.arrayOfPeople = []
  }

  async index({request, response, params}) {
    try {
      let varPeople = await People.findOrFail(params.id)
      await this.getChildren([varPeople]).then(console.log(this.arrayOfPeople))
      console.log(this.arrayOfPeople)
      return response.status(200).json({"message": "success", "status": 200, "data": children})
    } catch (exception) {
      return response.status(404).json({"message": "error", "status": 404})
    }

  }

  async getChildren(people = []) {
    people.forEach(async (item) => {
      this.arrayOfPeople.push(item.id)
      let child = await item.children().fetch()
      if (child.rows.length > 0) {
        return await this.getChildren(child.rows)
      }
    })
  }

let’s say i have 4 data in my table :

| id | name         | code         | parent_id | status | role      | join_date  | created_at          | updated_at          |
|----|--------------|--------------|-----------|--------|-----------|------------|---------------------|---------------------|
| 1  | TELE SPV01   | TELESPV001   | \N        | 1      | TELESPV   | 2018-01-01 | 2018-06-04 05:15:50 | 2018-06-04 05:15:50 |
| 2  | TELE ADMIN01 | TELEADMIN001 | 1         | 1      | TELEADMIN | 2018-01-01 | 2018-06-04 05:15:50 | 2018-06-04 05:15:50 |
| 3  | TELE SALES01 | TELESALES01  | 2         | 1      | TELESALES | 2018-01-01 | 2018-06-04 05:15:50 | 2018-06-04 05:15:50 |
| 4  | TELE ADMIN02 | TELEADMIN002 | \N        | 1      | TELESPV   | 2018-01-01 | 2018-06-04 05:15:50 | 2018-06-04 05:15:50 |

expected result is array with [1,2,3]

but i really can’t get the result… i hope someone kindly help me…

thank you very much

1 Like

What are you getting back?

an empty array… but actually i know the problem lies on javascript asynchronous nature, to make it simple, let me just show you another function :

  async getAllPeople() {
    let allPeople = await People.all()
    let myArray = []
    let checkChild = async (people) => {
      let eachPeopleChild = await people.children().fetch()
      if (eachPeopleChild.rows.length > 0) {
        return people
      }
      return false
    }
    allPeople.rows.forEach(async (people) => {
      let res = await checkChild(people)
      if (res !== false) {
        myArray.push(res)
      }
    })
    console.log(myArray)
    return myArray
  }

this function return an empty because it return or console as soon as this function is fired, obviously there is still some process running : forEach what i am expecting is, how can i return the result after the process is done, not while it running…

1 Like

Yes, you cannot call await inside a forEach loop. What you need is a for of.

Try this

constructor() {
  this.arrayOfPeople = []
}

async index({request, response, params}) {
  try {
    let varPeople = await People.findOrFail(params.id)
    await this.getChildren([ varPeople ])
    console.log(this.arrayOfPeople)
    return response.status(200).json({"message": "success", "status": 200, "data": children})
  } catch (exception) {
    return response.status(404).json({"message": "error", "status": 404})
  }
}

async getChildren(people = []) {
  for (let item of people) {
    this.arrayOfPeople.push(item.id)

    const child = await item.children().fetch()
    if (child.rows.length) {
       await this.getChildren(child.rows)
    }
  }
}
2 Likes

thank you very much, it works now… i will remember that never use forEach or map for the looping

You can use await in map like this:

const promices = array.map(async obj => {
	// await statements go here
});

await Promice.all(promices);

Not to mention, this is written in another async function.