studio-net / laravel-graphql

GraphQL implementation with power of Laravel
https://github.com/studio-net/laravel-graphql/
MIT License
56 stars 5 forks source link

How to filter relations #23

Closed DiederikvandenB closed 6 years ago

DiederikvandenB commented 6 years ago

Thanks for making this great package. It makes GraphQL even easier! However, I seem to have ran in to a problem. I was hoping I could make a filter function like this in my AlertDefinition:

    /**
     * @return array
     */
    public function getFilterable()
    {
        return [
            'active' => function (Builder $builder, $value) {
                if (!is_bool($value)) {
                    throw new InvalidArgumentException('Active filter must be of type boolean');
                }

                return $builder->whereActive($value);
        },
        ];
    }

And in my EndpointDefinition I would call the relation like this:

    /**
     * Which fields are queryable ?
     *
     * @return array
     */
    public function getFetchable()
    {
        return [
            'id'     => Type::id(),
            'alerts' => Type::listOf(\GraphQL::type('alert')),
        ];
    }

Now unfortunately, this does not work. The filter will be applied to the root alert query, but it will not allow me to filter like this:

query {
  endpoints {
   id,
   name,

   alerts (filter: {active: true}) {
     id
     created_at
   }
  }
}

Is there anyway to achieve this "automagically" instead of having to use a custom resolver? Because that would mean that if I would like to apply the same filter on the root alert query, I would either have to write the logic twice, or do some class extending / traits magic which seems rather cumbersome.

I suppose it would be ideal if something like this would be possible:

    /**
     * Which fields are queryable ?
     *
     * @return array
     */
    public function getFetchable()
    {
        return [
            'id'     => Type::id(),
            'alerts' => AlertDefinition::class,
        ];
    }

(How) can something like this be achieved?

cmizzi commented 6 years ago

Hi ! Thanks for using our package. Currently, the only way to manage that is to define the following fetchable entry :

/**
 * Returns fetchable endpoint fields
 *
 * @return array
 */
public function getFetchable() {
    return [
        // ...
        'alerts' => [
            'type' => \GraphQL::listOf('alert'),
            'args' => ['active' => Type::bool()],
            'inputable' => false
        ],
        // ...
    ];
}

/**
 * Resolve `alerts` field
 *
 * @param  Endpoint $root
 * @param  array $args
 * @return \Illuminate\Database\Eloquent\Collection
 */
public function resolveAlertsField(Endpoint $root, array $args) {
    return \App\Alert::whereActive($args['active'])->get();
}
query {
  endpoints {
   id,
   name,

   alerts (active: true) {
     id
     created_at
   }
  }
}

Some rewrites are scheduled to manage easily definition and transformer, to apply properly arguments, filters, etc. I'm working on that as soon as possible :)

DiederikvandenB commented 6 years ago

Thanks, that looks like a fine solution in the mean time, looking forward to the upcoming changes.

Another question, how would you tackle the N+1 problem when adding relations to definitions?

cmizzi commented 6 years ago

By defining a closure instead returning the built returned state. We're calling call_user_func only when needed.