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

Include related models with a custom scope #3453

Closed
3 tasks
bajtos opened this issue Jul 26, 2019 · 25 comments
Closed
3 tasks

Include related models with a custom scope #3453

bajtos opened this issue Jul 26, 2019 · 25 comments
Assignees
Labels
feature Relations Model relations (has many, etc.) Repository Issues related to @loopback/repository package
Milestone

Comments

@bajtos
Copy link
Member

bajtos commented Jul 26, 2019

Besides specifying the relation name to include, it's also possible to specify additional scope constraints:

  • custom order, limit, skip and fields
  • additional scope.where constraint

For example, the following filter will include only the first active product:

filter.include = [{
  relation: 'products',
  scope: {
    where: {active: true},
    limit: 1
  }
}

The initial version of Inclusion does not support custom scopes. In this story, we should improve findByForeignKeys function to support additional scope constraints.

LB3 test suite: loopback-datasource-juggler/test/include.test.js#L247-L253)

See also #3387

Acceptance criteria

  • support nested include relations.
  • add new mocha test cases to exercise these scenarios
  • update docs (add nexted query examples)
@bajtos bajtos added feature Relations Model relations (has many, etc.) Repository Issues related to @loopback/repository package labels Jul 26, 2019
@luncht1me
Copy link

luncht1me commented Oct 8, 2019

Was trying to do a filter query

include: [
{ relation: 'relatedModel', scope: { include:[{ relation: 'relatedRelatedModel' }] } },
]

but got shut down by 'scope not supported' error. I see it's written in the type definitions though, is this just a bad package version on my part perhaps? Or is scope within a relation actually not hashed out completely yet?

How should we be handling nested relationships? ie: I have a source base model w/ a hasmany to a mapping model, which has a hasmany mapping to another target model. Should I just custom spin a controller method to do it all manually for now? It would be great to just use the inclusion resolver like this.

edit ---- right I see in the source it's not implemented, my bad. I'll spin up a custom method to link it all together for now.

@ChrisKretschmer
Copy link

I have the same issue and need to query nested relations...
Is there no way atm to do this automatically? I have a tree structure and don't want to query for each node seperatly :(

@st119848
Copy link

i need inclusion in inclusion toooo. @luncht1me

@theophane-girard
Copy link

Me too ! :)

@tipsypastels
Copy link

Would really like to see this added.

@ericalves
Copy link

Is too hard use lb4 without nested include relations.. mostly if you came from lb3..

@ericalves
Copy link

Was trying to do a filter query

include: [
{ relation: 'relatedModel', scope: { include:[{ relation: 'relatedRelatedModel' }] } },
]

but got shut down by 'scope not supported' error. I see it's written in the type definitions though, is this just a bad package version on my part perhaps? Or is scope within a relation actually not hashed out completely yet?

How should we be handling nested relationships? ie: I have a source base model w/ a hasmany to a mapping model, which has a hasmany mapping to another target model. Should I just custom spin a controller method to do it all manually for now? It would be great to just use the inclusion resolver like this.

edit ---- right I see in the source it's not implemented, my bad. I'll spin up a custom method to link it all together for now.

Do you can show to us what you did to get around this problem?

@dhmlau dhmlau added the 2020Q1 label Nov 12, 2019
@dhmlau
Copy link
Member

dhmlau commented Nov 14, 2019

@agnes512 @bajtos, could you please add the acceptance criteria so that the team can estimate? Thanks!

@agnes512
Copy link
Contributor

@bajtos do we want to support all query clauses include, where, etc. or filter.include only in this task?

@mamiller93
Copy link

Including the where in this scope would greatly help us out. While we don't need it for our current implementation (admin users can see everything), we will need to start filtering our related model results in the coming few months and having the ability to scope/where would be huge. It would be a shame to have to drop to a direct SQL implementation.

@jannyHou
Copy link
Contributor

@agnes512 I believe this story only supports filter.include, the scope object is for the related items.

@samarpanB
Copy link
Contributor

+1 We also need the same. When can we expect this ? I see its in December milestone. Is it correct ?

Including the where in this scope would greatly help us out. While we don't need it for our current implementation (admin users can see everything), we will need to start filtering our related model results in the coming few months and having the ability to scope/where would be huge. It would be a shame to have to drop to a direct SQL implementation.

@dhmlau
Copy link
Member

dhmlau commented Nov 28, 2019

@samarpanB @mamiller93, we acknowledged the importance of having a custom scope. Thanks for your input!
@samarpanB, yes, the plan is if we could finish Reject create/update requests when data contains navigational properties in Nov which has PR under review, we (possibly @agnes512) can start working this one. The challenge is that the team will start going away for holidays at different point in Dec but will try our best.

@agnes512 agnes512 added this to the Dec 2019 milestone Dec 2, 2019
@agnes512 agnes512 self-assigned this Dec 4, 2019
@bajtos
Copy link
Member Author

bajtos commented Dec 5, 2019

do we want to support all query clauses include, where, etc. or filter.include only in this task?

I see two sub-features here:

(1) Filter which included models are returned, see the original issue description:

filter.include = [{
  relation: 'products',
  scope: {
    where: {active: true},
    limit: 1
  }
}]

(2) Recursive include (include models related to related models), as described in #3453 (comment):

filter.include = [{ 
  relation: 'relatedModel', 
  scope: {
    include: [{ 
      relation: 'relatedRelatedModel'
    }]
  },
}]

Ideally, we should support both flavors and also a combination of them. I think this should be actually easy to achieve, all we need to do is pass filter.include.scope to the relation resolver and the filter argument of find method called on the target repository. Fingers crossed 🤞

If my gut feeling is wrong, then we can pick first the option that is easier to implement and then decide what to do about the rest.

@agnes512
Copy link
Contributor

agnes512 commented Dec 9, 2019

closing as done

@agnes512 agnes512 closed this as completed Dec 9, 2019
@pratikjaiswal15
Copy link

pratikjaiswal15 commented Dec 21, 2019

Hello. Can you please tell us the syntax for nested relations for url. I have tried something like this but it is giving error 500.

http://[::1]:3000/users?filter[include][0][relation]=carts&filter[include][0][scope]filter[include][0][relation]=product

I have three models. Users have has-many carts and carts belongs-to product.
My loopback/cli version is 1.27.0.
Thank you in advance

@dougal83
Copy link
Contributor

Try incrementing the include filter as follows:

http://[::1]:3000/users?filter[include][0][relation]=carts&filter[include][1][scope][0]filter[where][product_id]=2

@pratikjaiswal15
Copy link

Still not working.

@dougal83
Copy link
Contributor

Still not working.

In that case you'll probably have to provide an example repo reproducing the problem so someone can take a look for you.

@pratikjaiswal15
Copy link

I have posted a question on stack overflow https://stackoverflow.com/questions/59435371/loopback-4-include-nested-relations
Whereas multiple relations working well. I would like to know the syntax for nested relation[rest-api] as it is not documented

@shadyanwar
Copy link

shadyanwar commented Dec 22, 2019

@bajtos @agnes512 thank you for the great work.
While removing fields works well, specifying which fields to only show results in nothing coming back in response from the target model at all (comes as undefined). Your help is appreciated.

Examples:
This one works well. Returns all properties except the username:

await exampleModelRepository.find({ include: [{ relation: "user", scope: {fields: { username: false }  } }] } )

This one doesn't work. Returns the included model's parent property as undefined. Obviously, the intended behavior is to only show the username:

await exampleModelRepository.find({ include: [{ relation: "user", scope: {fields: { username: true }  } }] } )

@agnes512
Copy link
Contributor

@pratikjaiswal15 hi, could you try to encode your filter with

encodeURIComponent(JSON.stringify({
  include: [
    {
      relation: 'carts',
      scope: {
        include: [{relation: 'product'}],
      },
    },
  ],
})
);

then do
http://[::1]:3000/users?filter= + the encode result ?

Sorry that we haven't found a proper way to query the nested relations with square brackets url format.
And since the query is getting complicated, we also recommend to just encode the filter with the above function.

@agnes512
Copy link
Contributor

agnes512 commented Dec 23, 2019

@shadyanwar I just tried it out and reproduced it.
I investigated a bit, it is interesting.. apparently the source key/foreign key needs to be included in the fields to make the inclusion work.

For example, for hasMany relation, Customer has many Orders, the foreign key Order.customerId needs to be included in the scope.fields

await customerRepo.find({
        include: [
          {relation: 'orders', scope: {fields: {name: true, customerId: true}}},
        ],
      });

Similarly, for belongsTo relation, an Order belongs to a Customer, the source id Customer.id needs to be included:

await orderRepo.find({
        include: [
          {relation: 'customer', scope: {fields: {name: true, id: true}}},
        ],
      });

I think our fields clause doesn't work well with such an inclusion use case, any thoughts? @strongloop/loopback-maintainers

@pratikjaiswal15
Copy link

@agnes512 No problem. Thank you.

@pratikjaiswal15 hi, could you try to encode your filter with

encodeURIComponent(JSON.stringify({
  include: [
    {
      relation: 'carts',
      scope: {
        include: [{relation: 'product'}],
      },
    },
  ],
})
);

then do
http://[::1]:3000/users?filter= + the encode result ?

Sorry that we haven't found a proper way to query the nested relations with square brackets url format.
And since the query is getting complicated, we also recommend to just encode the filter with the above function.

No problem. Thank you

@fabripeco
Copy link

fabripeco commented Nov 19, 2020

Hi,

I'm wondering if the 'limit' param works if added to the scope of nested relations, to limit the number of items of a hasMany relation. I have a strange behaviour in the response.
I have a BusinessPartner model with hasMany relation to Addresses

@hasMany(() => BuspartnerAddress, {keyTo: 'clientId', name: 'addresses'})
addresses: BuspartnerAddress[];

I want to get the businessPartners with only the first address for each one. My filter param looks like

{
  "order": ["name ASC"],
  "where": {
    "bustype": "CUSTOMER"
  },
  "include": [
    {
      "relation": "addresses",
      "scope": {
        "skip": 0,
        "limit": 1,
        "where": {
          "deleted": false
        }
      }
    }
  ]
}

The where clause in the scope of the include works well, but the limit: 1 param returns the address for only one businessPartner, not one for each businessPartner. If I increase the value of the limit, e.g. 10, the response returns max 10 address, differently distribuited on the businessPartners.
It looks like the query on the related models was performed only once and not for each 'parent' instance (businessPartner).

Am I wrong in something? Is this the intended behaviour?

I'm using a postgresql connector and this is my ecosystem (Nodejs v12.16.2 npm v6.14.4 - Postresql 12)

├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── [email protected]
├── [email protected]

Thank you very much

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Relations Model relations (has many, etc.) Repository Issues related to @loopback/repository package
Projects
None yet
Development

No branches or pull requests