Loopback-next: hasOne() and belongsTo() not working in loopback4

Created on 3 Jan 2019  路  8Comments  路  Source: strongloop/loopback-next

I have try with following code but i will got only first table record, there are missed second table info.

person.model.ts:

  @property({
     name: 'name',
    type: 'string',
  })
  name: string;

 @hasOne(() => Info)
  infos: Info;

info.model.ts:

@property({
   type: 'number',
   id: true,
   required: true,
 })
 id: number;

  @belongsTo(() => Person)
   personId?: number;

person.repository.ts

 export class PersonRepository extends DefaultCrudRepository<Person,typeof Person.prototype.id> 
 {
     public readonly infos: HasOneRepositoryFactory<Info,typeof Person.prototype.id>;
     constructor(@inject('datasources.db') dataSource: DbDataSource,    
             @repository.getter('InfoRepository')
               getInfoRepository: Getter<InfoRepository>,) {
       super(Person, dataSource);
       this.infos = this._createHasOneRepositoryFactoryFor('infos',getInfoRepository);
    }
 }

Current Behavior

But when i will call the api using explorer they have return following schema

Example Value
 Schema
[  {
   "id": 0,
   "name": "string"
}]

Is there any solution for this?

Relations needs steps to reproduce

Most helpful comment

I have create one simple api which is going to fetch the data from database(Mysql).

 Table 1 : person (id, person_name )
 Table 2 : info (id, person_id, address)

Table 1 is belongs to table one. Suppose we fetch data from person table then respective info table data will fetch.

person.model.ts:

import { Info } from "./info.model";

import { Entity, model, property, hasMany, hasOne } from '@loopback/repository';

@model()
export class Person extends Entity {
  @property({ type: 'number', id: true, required: true, })
  id: number;

  @property({ person_name: 'name', type: 'string', })
  person_name: string;

  @hasOne(() => Info, { keyTo: 'person_id' })
  infos: Info[];

  constructor(data?: Partial<Person>) {
    super(data);
  }
}

info.model.ts:

import { Entity, model, property, belongsTo } from '@loopback/repository';
import { Person } from "./person.model";

@model()
export class Info extends Entity {

  @property({ type: 'number', id: true, required: true, })
  id: number;

  @property({ address: 'name', description: "Info name.", type: 'string', })
  address: string;

  @belongsTo(() => Person)
  person_id: number;

  constructor(data?: Partial<Info>) {
    super(data);
  }
}

person.repository.ts

import { DefaultCrudRepository, repository, juggler, HasManyRepositoryFactory, HasOneRepositoryFactory } from '@loopback/repository';
import { InfoRepository } from './info.repository';

import { Person, Info } from '../models';
import { DbDataSource } from '../datasources';
import { inject, Getter } from '@loopback/core';

export class PersonRepository extends DefaultCrudRepository<Person, typeof Person.prototype.id> {
  public infos: HasOneRepositoryFactory<Info, typeof Person.prototype.id>;
  constructor(
    @inject('datasources.db') dataSource: DbDataSource, @repository.getter('InfoRepository')
    protected infoRepositoryGetter: Getter<InfoRepository>
  ) {
    super(Person, dataSource);
    this.infos = this._createHasOneRepositoryFactoryFor('infos', infoRepositoryGetter);
  }
}

Api code which is called from controller

@get('/people', {
    responses: {
      '200': {
        description: 'Array of Person model instances',
        content: {
          'application/json': {
            schema: { type: 'array', items: { 'x-ts-type': Person } },
          },
        },
      },
    },
  })
  async find(
    @param.query.object('filter', getFilterSchemaFor(Person)) filter?: Filter): Promise<Person[]> {
    return await this.personRepository.find(filter);
  }

It will return error : 500 TypeError: Cannot read property 'target' of undefined

Just i want to get person information with address.

All 8 comments

@raymondfeng Can you please help me in this if you have brief idea ?

Hi @drplutus, please create a small app reproducing the issue per our bug reporting instructions.

@b-admike I think you are most familiar with relations, could you PTAL?

I have create one simple api which is going to fetch the data from database(Mysql).

 Table 1 : person (id, person_name )
 Table 2 : info (id, person_id, address)

Table 1 is belongs to table one. Suppose we fetch data from person table then respective info table data will fetch.

person.model.ts:

import { Info } from "./info.model";

import { Entity, model, property, hasMany, hasOne } from '@loopback/repository';

@model()
export class Person extends Entity {
  @property({ type: 'number', id: true, required: true, })
  id: number;

  @property({ person_name: 'name', type: 'string', })
  person_name: string;

  @hasOne(() => Info, { keyTo: 'person_id' })
  infos: Info[];

  constructor(data?: Partial<Person>) {
    super(data);
  }
}

info.model.ts:

import { Entity, model, property, belongsTo } from '@loopback/repository';
import { Person } from "./person.model";

@model()
export class Info extends Entity {

  @property({ type: 'number', id: true, required: true, })
  id: number;

  @property({ address: 'name', description: "Info name.", type: 'string', })
  address: string;

  @belongsTo(() => Person)
  person_id: number;

  constructor(data?: Partial<Info>) {
    super(data);
  }
}

person.repository.ts

import { DefaultCrudRepository, repository, juggler, HasManyRepositoryFactory, HasOneRepositoryFactory } from '@loopback/repository';
import { InfoRepository } from './info.repository';

import { Person, Info } from '../models';
import { DbDataSource } from '../datasources';
import { inject, Getter } from '@loopback/core';

export class PersonRepository extends DefaultCrudRepository<Person, typeof Person.prototype.id> {
  public infos: HasOneRepositoryFactory<Info, typeof Person.prototype.id>;
  constructor(
    @inject('datasources.db') dataSource: DbDataSource, @repository.getter('InfoRepository')
    protected infoRepositoryGetter: Getter<InfoRepository>
  ) {
    super(Person, dataSource);
    this.infos = this._createHasOneRepositoryFactoryFor('infos', infoRepositoryGetter);
  }
}

Api code which is called from controller

@get('/people', {
    responses: {
      '200': {
        description: 'Array of Person model instances',
        content: {
          'application/json': {
            schema: { type: 'array', items: { 'x-ts-type': Person } },
          },
        },
      },
    },
  })
  async find(
    @param.query.object('filter', getFilterSchemaFor(Person)) filter?: Filter): Promise<Person[]> {
    return await this.personRepository.find(filter);
  }

It will return error : 500 TypeError: Cannot read property 'target' of undefined

Just i want to get person information with address.

First of all, filter.include is not supported in LB4 yet, see https://github.com/strongloop/loopback-next/issues/1352 and the spike/proof-of-concept in https://github.com/strongloop/loopback-next/pull/2124.

But when i will call the api using explorer they have return following schema

Generating the correct schema for different CRUD endpoints is a bit tricky:

  • When creating a new "Person" model or editing an existing one, the payload must not contain "info" property because LB4 does not support creating related models.
  • When querying "Person" models, the response may contain "info" property with the related data.

I think we need two schemas, possibly even two TypeScript models/interfaces. Please refer to discussion in https://github.com/strongloop/loopback-next/pull/2124 for more details.

/cc @jannyHou @b-admike

It will return error : 500 TypeError: Cannot read property 'target' of undefined

Sorry, I don't have bandwidth to re-create a working LB4 application from your instructions and thus I cannot look into details of the error you are encountering. Can you please follow the instructions in https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-4x-bugs and provide us with a LB4 app that we can simply git clone and then run to reproduce the problem.

try,
@hasOne(() => Info) infos: Info[];

instead of
@hasOne(() => Info, { keyTo: 'person_id' }) infos: Info[];

may be it will work

I spot some problems that might crash the app:

  1. The type of hasOne property is an object instead of an array( has one).
  @hasOne(() => Info, { keyTo: 'person_id' })
  infos: Info;  // here
  1. You are not using the default foreign key names (i.e person_id in your case. default is personId).
    To customize it, you need to do the following:
    in Person.model:
  @hasOne(() => Info, { keyTo: 'person_id' })
  infos: Info;

in Info.model:

  @belongsTo(() => Customer, {name: 'person'}) // `person` is the name of your belongsTo relation name
  person_id?: number;

Ref: HasOne relation customizing name

Also, we support inclusion for relations now :D check out Querying related models on our site!

Feel free to re-open the issue if it's not solved yet. Thanks!

this works for me

@model({
  settings: {
    foreignKeys: {
      fk_todo_todoListId: {
        name: 'fk_todo_todoListId',
        entity: 'TodoList',
        entityKey: 'id',
        foreignKey: 'todolistid',
      },
    },
  },
})
export class Todo extends Entity {
  //etc.
}

https://loopback.io/doc/en/lb4/todo-list-tutorial-sqldb.html#specify-the-foreign-key-constraints-in-todo-model

Was this page helpful?
0 / 5 - 0 ratings