cloudcreativity/laravel-json-api

How I can filter included resources by their fields

sunnydesign opened this issue · 8 comments

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.

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.

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

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

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

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.

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

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

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.