0

I'm setting up an api using LoopBack 4 and its mysql connector. So I have a model Plane, which has a @hasMany relation with pilots :

    class Plane extends Entity {
     @property({
      id: true,"dataPrecision": 10, "dataScale": 0, "nullable": "N" },
     })
     id: number;

     @property()
     name: string;

     @hasMany(() => Pilot, { keyTo: 'planeId' })
     pilots?: Array<Pilot>;

So now what I wanted to do is to create a plane and add it its pilots on a single request. In my plane repository I made something like this :

class PlaneRepository extends DefaultCrudRepository<...> {
  planes;

  constructor(
    @inject('datasources') dataSource: any,
    @repository.getter('PilotRepository') getPilotRepository
  ) {
    this.planes = this.createHasManyRepositoryFactoryFor('planes', getIpilotRepository);
  }

And my controller looks like that:

class PlaneController {
  @post('/project', {
    responses: {
      '200': {
        description: 'Plane model instance',
        content: { 'application/json': { schema: getModelSchemaRef(Project) }        
      },
    },
  })
  async create(
    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(Plane, { exclude: ['id'], includeRelations: true }),
        },
      },
    })
    plane: Omit<Plane, 'id'>,
  ): Promise<plane> {
    return this.planeRepository.create(plane);
  }
}

But when I try to call my route with something like that

{ name: 'Jet 27', pilots: [ { id: 0, name: Chuck Berry } ] }

I have a 422 error:

"The Plane instance is not valid. Details: pilots is not defined in the model (value: undefined)."

I don't know if this is the expected behaviour, I must admit that I'm a bit confused with the relations way of functionning, but if it is, then how am I supposed to do.

Bill P
  • 3,622
  • 10
  • 20
  • 32
CeBa
  • 46
  • 3
  • I think above implements `include` filter. AFAIK, it is not available yet in `loopback4` – Salitha Oct 01 '19 at 09:53
  • You might be right... Therefore, should I firstly create my plane, and then make a second call to create its attached pilots ? – CeBa Oct 01 '19 at 10:02
  • Yeah, It is what I did. Performance is very poor but it seems the only option. – Salitha Oct 01 '19 at 10:12
  • Well that's annoying, but thanks for your answer anyway @SalithaIndrajithPathiraja – CeBa Oct 01 '19 at 12:07

1 Answers1

0

When creating a model like your plane from the example above, it is not intended to pass navigational properties like pilots in the requestBody. There is even a recently landed feature which rejects such requests as is can not be handled.

If you really want to handle such complex requests, e.g to save multiple client requests to different endpoints, you can implement such behaviour directly in your controller class, but you have to remove the navigational property from the object the planeRepository gets passed (as this would throw an exception).

Example (not tested):

  @post('/project', {
    responses: {
      '200': {
        description: 'Plane model instance',
        content: { 'application/json': { schema: getModelSchemaRef(Project) }        
      },
    },
  })
  async create(
    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(PlaneWithRelations, { exclude: ['id'] }),
        },
      },
    })
    plane: Omit<PlaneWithRelations, 'id'>,
  ): Promise<Plane> {
    const plane = await this.planeRepository.create({ name: plane.name });
    for (const pilot of planes.pilots) {
      await this.pilotRepository.create({ ...pilot, planeId: plane.id });
    }
    return plane;
  }

It should also be possible to group the changes in a database transaction.

derdeka
  • 226
  • 1
  • 5