Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(repository): add InclusionResolver type and includeRelatedModels #3517

Merged
merged 1 commit into from Aug 19, 2019

Conversation

agnes512
Copy link
Contributor

@agnes512 agnes512 commented Aug 7, 2019

resolves #3445

When reviewing, select hide white space changes would make it easier to review 😄

  • Add InclusionResolver interface, includeRelatedModels and isInclusionAllowed helpers.
  • Used stubbed resolvers for the unit tests.

Checklist

👉 Read and sign the CLA (Contributor License Agreement) 👈

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

👉 Check out how to submit a PR 👈

@nabdelgadir nabdelgadir force-pushed the inclusionresolver branch 3 times, most recently from 2467176 to 273646d Compare August 9, 2019 19:38
@nabdelgadir nabdelgadir added feature Repository Issues related to @loopback/repository package labels Aug 9, 2019
@agnes512 agnes512 force-pushed the inclusionresolver branch 2 times, most recently from ebd6559 to 9151086 Compare August 12, 2019 16:54
@agnes512 agnes512 marked this pull request as ready for review August 12, 2019 17:23
Copy link
Contributor

@jannyHou jannyHou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎 Great start! I left a few comments.

@nabdelgadir nabdelgadir force-pushed the inclusionresolver branch 3 times, most recently from fe33565 to f455a65 Compare August 13, 2019 13:47
@nabdelgadir nabdelgadir changed the title feat(repository): add interface inclusionResolver and the helper feat(repository): add InclusionResolver type and the helper Aug 14, 2019
@nabdelgadir nabdelgadir changed the title feat(repository): add InclusionResolver type and the helper feat(repository): add InclusionResolver type and its helpers Aug 14, 2019
@nabdelgadir nabdelgadir changed the title feat(repository): add InclusionResolver type and its helpers feat(repository): add InclusionResolver type and includeRelatedModels Aug 14, 2019
@@ -100,6 +101,8 @@ export class DefaultCrudRepository<
> implements EntityCrudRepository<T, ID, Relations> {
modelClass: juggler.PersistedModelClass;

public inclusionResolvers: Map<string, InclusionResolver<T, Entity>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public readonly inclusionResolvers: Map<string, InclusionResolver<T, Entity>> = new Map();

/**
* List of source models as returned by the first database query.
*/
sourceEntities: S[],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if sourceEntities needs to have a generic type S. It's not helping the compiler much.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raymondfeng I suggested to add a generic type S to avoid specifying the source entity's type like

const resolver: InclusionResolver = async entities => {
        // you need to specify Category....
        const c = entities[0] as Category;
        const product: Product[] = await categoryRepo.products(c.id).find();
        return [product];
      };

      categoryRepo.inclusionResolvers.set('products', resolver);

In the resolver function.

See the current resolver function

vs

previous resolver function

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. It sounds good then.

Copy link
Contributor

@jannyHou jannyHou Aug 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second thought...maybe the resolver should be written as

const hasManyResolver: InclusionResolver = async (entities: Category[]) => {
    const products: Product[] = [];

    for (const category of entities) {
      const product = await categoryRepo.products(category.id).find();
      products.push(product);
    }
    return products;
  };

?
Then we don't need the generic type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current signature with S seems to be better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, generic arguments are pretty useless here. At high level, we need a registry of resolvers, mapping from relation name (e.g. products) to a resolver. This registry cannot use generic types and has to fall back to Entity.

That's why I am using the following definition in my spike:

https://github.com/strongloop/loopback-next/blob/2fa5df67181cdcd23a5dce90c9c640fe75943cb8/packages/repository/src/relations/relation.types.ts#L121-L134

I am concerned that the current design creates a false sense of safety - the resolver is implemented with the assumption that it will be called with certain S and T types, but in reality the code calling it will always allow any Entity.

Having said that, I see how the stubbed resolvers are easier to implement when generic arguments are in place.

I don't have enough insight now to be able to judge what aspect is more important.

Copy link
Member

@bajtos bajtos Aug 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, I take my comment back. When I reviewed code below, I found that it is able to provide a meaningful value for S.

public readonly inclusionResolvers: Map<
  string,
  InclusionResolver<T, Entity>
> = new Map();

Nice trick! I agree that we should keep the generic argument for the type of the source entity.

The remaining question is whether we need the second generic argument for the type of the target (related) entity?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would help the developer with the return type for each resolver (e.g. restricting to type Product), but I don't think it's too important.

Copy link
Member

@bajtos bajtos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the production code, will try to review the tests tomorrow.


describe('findByForeignKeys', () => {
describe('relation helpers', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file is growing quite large. Can we split it into two files please? One test file for findByForeignKeys (this can be hopefully just a rename from relation.helpers.unit.ts to something like find-by-foreing-key.ts or relations-helpers/find-by-foreign.keys, with no code changes needed), another test file for the new helper includeRelatedModels.

/**
* List of source models as returned by the first database query.
*/
sourceEntities: S[],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, generic arguments are pretty useless here. At high level, we need a registry of resolvers, mapping from relation name (e.g. products) to a resolver. This registry cannot use generic types and has to fall back to Entity.

That's why I am using the following definition in my spike:

https://github.com/strongloop/loopback-next/blob/2fa5df67181cdcd23a5dce90c9c640fe75943cb8/packages/repository/src/relations/relation.types.ts#L121-L134

I am concerned that the current design creates a false sense of safety - the resolver is implemented with the assumption that it will be called with certain S and T types, but in reality the code calling it will always allow any Entity.

Having said that, I see how the stubbed resolvers are easier to implement when generic arguments are in place.

I don't have enough insight now to be able to judge what aspect is more important.

/**
* List of source models as returned by the first database query.
*/
sourceEntities: S[],
Copy link
Member

@bajtos bajtos Aug 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, I take my comment back. When I reviewed code below, I found that it is able to provide a meaningful value for S.

public readonly inclusionResolvers: Map<
  string,
  InclusionResolver<T, Entity>
> = new Map();

Nice trick! I agree that we should keep the generic argument for the type of the source entity.

The remaining question is whether we need the second generic argument for the type of the target (related) entity?

@nabdelgadir nabdelgadir force-pushed the inclusionresolver branch 4 times, most recently from 4a445c8 to 82a7a23 Compare August 16, 2019 19:53
… helper function

Add InclusionResolver type, includeRelatedModels and isInclusionAllowed helpers.

Co-authored-by: Nora <nora.abdelgadir@ibm.com>
Co-authored-by: Miroslav Bajtoš <mbajtoss@gmail.com>
Copy link
Contributor

@jannyHou jannyHou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 🚢

@nabdelgadir nabdelgadir merged commit c9c39c9 into master Aug 19, 2019
@nabdelgadir nabdelgadir deleted the inclusionresolver branch August 19, 2019 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Repository Issues related to @loopback/repository package
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce InclusionResolver concept
5 participants