Loopback-next: belongsToMany relation to model of the same type fails

Created on 4 Dec 2018  路  8Comments  路  Source: strongloop/loopback-next

From discussion on #1571

Description

I have a model called Category with a parentId field that points to the parent Category

Category Model:

export class Category extends Entity {
  @property({
    type: 'number',
    id: true,
    generated: true
  })
  id?: number;

  @belongsTo(() => Category)
  parentId: number;

  @belongsTo(() => Domain)
  containerId: number;

  @property({
    type: 'boolean',
    default: false,
  })
  internal?: boolean;

  @property({
    type: 'string',
    required: true,
  })
  title: string;

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

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

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

I created a /categories/{id}/parent endpoint per the docs

When I call that endpoint I get this error:

Unhandled error in GET /categories/2/parent: 500 Error: Circular dependency detected: controllers.CategoryParentController --> @CategoryParentController.constructor[0] --> repositories.CategoryRepository --> @CategoryRepository.constructor[2] --> repositories.CategoryRepository

Category Parent Controller:

export class CategoryParentController {
  constructor(@repository(CategoryRepository) protected categoryRepository: CategoryRepository) { }
  @get('/categories/{id}/parent')
  async getCustomer(
    @param.path.number('id') id: number,
  ): Promise<Category> {
    return await this.categoryRepository.parent(id);
  }
}

Category Repository

export class CategoryRepository extends DefaultCrudRepository<
  Category,
  typeof Category.prototype.id
> {
  public readonly container: BelongsToAccessor<Domain, number>;
  public readonly parent: BelongsToAccessor<Category, number>;
  constructor(
    @inject('datasources.db') dataSource: DbDataSource,
    @repository.getter(DomainRepository)
    protected domainRepositoryGetter: Getter<DomainRepository>,
    @repository.getter(CategoryRepository)
    protected categoryRepositoryGetter: Getter<CategoryRepository>
  ) {
    super(Category, dataSource);
    this.container = this._createBelongsToAccessorFor('container', domainRepositoryGetter);
    this.parent = this._createBelongsToAccessorFor('parent', categoryRepositoryGetter);
  }
}

Note that the container part works as expected, only parent is broken.

Docs Relations good first issue

Most helpful comment

I just realized that if the above CategoryRepository has a hasMany relation to DocumentRepository and DocumentRepository has a belongsTo relation to CategoryRepository then you get a circular dependency error.
What should be done in this case as the Getter.fromValue(this) isnt really usefull here?

All 8 comments

In your CategoryRepository, you are injecting a getter for obtaining the same repository again.

Since your relation is referring back to the same model, you can reuse the same repository.

Could you please check if the following code fixes to problem for you?

export class CategoryRepository extends DefaultCrudRepository<
  Category,
  typeof Category.prototype.id
> {
  public readonly container: BelongsToAccessor<Domain, number>;
  public readonly parent: BelongsToAccessor<Category, number>;
  constructor(
    @inject('datasources.db') dataSource: DbDataSource,
    @repository.getter(DomainRepository)
    protected domainRepositoryGetter: Getter<DomainRepository>,
  ) {
    super(Category, dataSource);
    this.container = this._createBelongsToAccessorFor('container', domainRepositoryGetter);
    this.parent = this._createBelongsToAccessorFor('parent', Getter.fromValue(this));
  }
}

I think you won't be the last person encountering this issue, let's describe the problem & the solution in our docs, e.g. hasMany relation and belongsTo relation. (Feel free to propose other places where you would be looking for this information.)

Would you @hvlawren like to contribute this documentation change yourself?

That fixed it!

I'd say those two places probably make the most sense.

I've got a lot on my plate right now, so I'd suggest someone else add the docs.

That fixed it!

Great! 馃帀

I'd say those two places probably make the most sense.

I've got a lot on my plate right now, so I'd suggest someone else add the docs.

We all have a lot on our plates, don't we? 馃槈

It would be great if you could find some time later to contribute back, but I will understand if you don't.

Let's keep this issue open, I think it's a good opportunity for new contributors.

Well, I am sniffing around for good first issues. Will take this up.

Hello.
I made a PR to update document for this issue: https://github.com/strongloop/loopback-next/pull/2196
Please help me check it.

I just realized that if the above CategoryRepository has a hasMany relation to DocumentRepository and DocumentRepository has a belongsTo relation to CategoryRepository then you get a circular dependency error.
What should be done in this case as the Getter.fromValue(this) isnt really usefull here?

@mmeilby: do you found a solution for the case

@mmeilby: do you found a solution for the case

Yes, the solution in May was to add this line before the repository class definition:
@bind({scope: BindingScope.SINGLETON})
export class SomeRepository extends DefaultTransactionalRepository ...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

acrodrig picture acrodrig  路  3Comments

half-blood-programmer picture half-blood-programmer  路  3Comments

cloudwheels picture cloudwheels  路  3Comments

ThePinger picture ThePinger  路  3Comments

rexliu0715 picture rexliu0715  路  3Comments