cloudcreativity / laravel-json-api

JSON API (jsonapi.org) package for Laravel applications.
http://laravel-json-api.readthedocs.io/en/latest/
Apache License 2.0
778 stars 109 forks source link

How I can filter included resources by their fields #631

Closed sunnydesign closed 2 years ago

sunnydesign commented 2 years ago

Hi! I want to filter included resources by their fields. I tried to use withFilters() for the relationship fields, but it didn’t work: For example, for this endpoints I want to filter related cart-items by their field practiceLocationId and to include them only: http://localhost/api/v1/distributors?include=cart-items&filters[practiceLocationId]=89

My schema:

class DistributorSchema extends BaseSchema
{
...
    public function fields(): array
    {
          ...
          HasMany::make('cart-items')->withFilters(
              Where::make('practiceLocationId')
          )
          ...
    }
...
}

Big thanks for your answer.

lindyhopchris commented 2 years ago

Hi! I think you've created this issue in the wrong repo - from the code example I think you're using laravel-json-api/laravel?

In answer to your question though, there is nothing in the JSON:API spec that allows you to filter included resources. So this isn't supported. Filters apply to the primary set of resources, that appears in the data member of the document.

sunnydesign commented 2 years ago

It's so bad, my friend. I'm very disapointed. How can I solve this problem? Maybe you tell me a solution?

sunnydesign commented 2 years ago

You right, I use laravel-json-api/laravel. But I always write here :)

lindyhopchris commented 2 years ago

So you probably need to explain your use case as there's most likely a different way to do this. For example, you can filter a relationship endpoint, so you could do:

GET /api/v1/distributors/123/cart-items?filter[practiceLocationId]=456
sunnydesign commented 2 years ago

So, my goal is to receive in one request all distributors with their cart-items which deep related to distributors through product-distributors. Related cart-items must be filtered by practiceLocationId field, which belong cart-items resource. And distributors must be filtered by cart-items.practiceLocationId to.

sunnydesign commented 2 years ago

This is what I want to get with Eloquent:

$distributors = \App\Models\Distributor::whereHas('cartItems', function(Illuminate\Database\Eloquent\Builder $query) {
    $query->where('cart_item.practice_location_id', '=', 1);
});

foreach($distributors->get() as $distributor) {
    echo '<strong>distributor.id: ' . $distributor->id . '</strong>';
    echo '<br />';

    foreach ($distributor->cartItems()->where('cart_item.practice_location_id', '=', 1)->get() as $cartItem) {
        echo 'cartItem.id: ' . $cartItem->id . ' cartItem.practice_location_id: ' . $cartItem->practice_location_id;
        echo '<br />';
    }

    echo '<br />';
}

And result:

distributor.id: 30
cartItem.id: 222 cartItem.practice_location_id: 1
cartItem.id: 1 cartItem.practice_location_id: 1

distributor.id: 33
cartItem.id: 255 cartItem.practice_location_id: 1

distributor.id: 27
cartItem.id: 361 cartItem.practice_location_id: 1

distributor.id: 23
cartItem.id: 484 cartItem.practice_location_id: 1
lindyhopchris commented 2 years ago

So it's unrealistic to do that in one HTTP request using the JSON:API spec. Your example code of doing it with Eloquent has 1 database query to get the distributors, then an additional database query for each distributor to get the required cart items.

You've got an N+1 problem there, as the more distributors you have, the more database queries you'd get.

There's nothing in the JSON:API spec that allows you to do that at the moment in a single request. So you'd either need to accept multiple HTTP requests (which is along the lines of the N+1 problem you've got in the above example). Or you'd need to package the data up yourself in your own controller action. You can add custom actions, they are documented here: https://laraveljsonapi.io/docs/1.0/routing/custom-actions.html

sunnydesign commented 2 years ago

Yes, i know about N+1, my code was for example. Anyway, my problem has already been solved by another way. Big thanks for your answers.