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

Filter results by existence of relation #4

Closed
mrodriguez-bc opened this issue Feb 9, 2022 · 6 comments
Closed

Filter results by existence of relation #4

mrodriguez-bc opened this issue Feb 9, 2022 · 6 comments
Labels
enhancement New feature or request released

Comments

@mrodriguez-bc
Copy link

mrodriguez-bc commented Feb 9, 2022

How would I go about filtering results where the condition of a nested relationship exists? I've looked through the docs and tried various approaches to no avail.

Example:

Let's say we have Authors and Books, and we want to filter Authors by existence of a Book with a genre.

Authors

{ id: 1, name: "John" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Mary" }

Books

{ id: 1, author_id: 1, title: "Foo", genre: "Fiction" },
{ id: 2, author_id: 1, title: "Bar", genre: "SciFi" },
{ id: 3, author_id: 2, title: "Some Book", genre: "Non-Fiction" },
{ id: 4, author_id: 3, title: "Other Book", genre: "Fiction" },
{ id: 5, author_id: 3, title: "Another Book", genre: "Non-Fiction" },
{ id: 6, author_id: 3, title: "Yet Another Book", genre: "SciFi" },

Suppose we query Authors where genre = Fiction, only Author ID 1 and 3 are returned.

In my real use case, it's not Authors and Books, but Assets and Events.

Query:

query Assets($limit: Int, $offset: Int, $eventsFilter: Filter) {
  assets(limit: $limit, offset: $offset, filter: $filter) {
    name
    events(filter: $eventsFilter) {
      id
      event_type
    }
  }
}

Variables:

{
  "limit": 5,
  "offset": 0,
  "eventsFilter": {
    "event_type": "created"
  }
}

returns:

"data": {
  "assets": [
    {
      "name": "Squish #4644",
      "events": []
    },
    {
      "name": "Squish #4651",
      "events": []
    },
    {
      "name": "Squish #4667",
      "events": []
    },
    {
      "name": "Squish #4676",
      "events": []
    },
    {
      "name": "Squish #4684",
      "events": []
    }
  ]
}
}

But I want these assets to be excluded, because they don't meet the Events criteria.

@IlyaSemenov
Copy link
Owner

IlyaSemenov commented Feb 10, 2022

How would I go about filtering results where the condition of a nested relationship exists?
Let's say we have Authors and Books, and we want to filter Authors by existence of a Book with a genre.

First of all, this library in its simpler use is a thin wrapper around objection.js and its withGraphFetched. How would you achieve that with objection in the first place? I don't think that is possible.

Second, with your GraphQL query, that is also not possible. Consider the two queries:

query Assets1($limit: Int, $offset: Int, $eventsFilter: Filter) {
  assets(limit: $limit, offset: $offset, filter: $filter) {
    name
    events(filter: $eventsFilter) {
      id
      event_type
    }
  }
}

query Assets2($limit: Int, $offset: Int, $eventsFilter: Filter) {
  assets(limit: $limit, offset: $offset, filter: $filter) {
    name
  }
}

They obviously are supposed to return the same set of assets. It would be very counter intuitive if the first query returned less assets than the second.

So my recommendation would be adding a new parameter to the assets query:

query Assets1($limit: Int, $offset: Int, $events_filter: Filter) {
  assets(limit: $limit, offset: $offset, filter: $filter, eventsFilter: $eventsFilter) {
    name
    # events could be included or could be omitted - that doesn't affect the assets query!
    events(filter: $eventsFilter) {
      id
      event_type
    }
  }
}

and then creating a custom query:

Assets: async (parent, args, context, info) => {
  const assets = await resolveGraph(
    context,
    info,
    Asset.query().whereExists(
      // See https://vincit.github.io/objection.js/guide/query-examples.html
      Assets.relatedQuery('events').where(
        /* construct custom condition from args.eventsFilter */
      )
    )
  )
  return assets
}

Honestly I don't remember if there is an exported helper for constructing a condition from a Filter object. I quickly looked up and see filter.js but it's apparently not publicly exported. In the meanwhile you can construct the condition manually (with something like .where({ event_type: args.eventsFilter.event_type }), or perhaps using a callback where form if you need to handle optional arguments).

Do you think this will work for you?

I'll keep this issue opened because having a helper for that would be a good addition.

@IlyaSemenov IlyaSemenov added the enhancement New feature or request label Feb 10, 2022
@mrodriguez-bc
Copy link
Author

Thanks for getting back so quickly :)

This is brilliant! I was able to achieve what I need by adding an additional argument to the root resolver, and then using query.whereExists(Model.relatedQuery(relation).where(filter)) with a little bit of extra logic to make this bit reusable depending on the argument values.

Perhaps you are right about my intention not making sense -- maybe I explained it poorly, or maybe I could simply achieve what I wanted in a better manner. It's useful, though, because now I can query the Assets resolver for Assets that have a specific type of Event.

query Assets($limit: Int, $filter: AssetFilter, $eventsFilter: EventFilter) {
  assets(limit: $limit, filter: $filter, eventsFilter: $eventsFilter) {
    id
    name
    events(filter: $eventsFilter) {
      id
      event_type
    }
  }
}
{
  "limit": 10,
  "eventsFilter": {
    "event_type": "created"
  }
}

This now does exactly what I wanted :)

That being said, I'd be happy to contribute to this library any time. It's a great plugin for Objection, and I'd love to see this continue being maintained.

Thanks again!

@mrodriguez-bc
Copy link
Author

Is it possible to access the arguments passed into a child relation, and expose a query builder to handle them? I see that relations can handle the built-in filters, paginators, and modifiers, but having access to a query builder for additional arguments would be super helpful — I’m just not sure if this is already doable, and I’m missing something in the docs that explains how. Otherwise, I seem to only be able to access the root arguments within the corresponding resolver, which also limits me to tailoring the root query builder. I also realize this is a lot more complicated, but either a little bit of guidance I’d gladly submit a pull request to make it possible if it isn’t already doable out of the box.

@IlyaSemenov
Copy link
Owner

IlyaSemenov commented Feb 14, 2022

@mrodriguez-bc I think that's what RelationResolver({ modifier: ... }) does?

Edit: ah, I didn't real well, you want to access the arguments. Apparently that's not possible:

https://github.com/IlyaSemenov/objection-graphql-resolver/blob/10c67fae16c59edcbae675d3ebf3a26cb29263b2/src/resolver/relation.ts#L53-L55

I don't see args nearby in the execution context so can't instantly tell what's the best course of actions... I was distracted with something else and haven't touched this library for a year, so need to refresh my understanding of the architecture first. I'm happy to accept a PR. I may have time to revisit this myself as well.

@github-actions
Copy link
Contributor

🎉 This issue has been resolved in version 4.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@IlyaSemenov
Copy link
Owner

@mrodriguez-bc modifiers now accept second argument which is a GraphQL resolve tree object. You can access GraphQL args for the field being resolved with { args } on it:

export const Book = ModelResolver(BookModel, {
  fields: {
    id: true,
    title: true,
    authors: RelationResolver({
      modifier(authors, { args }) {
        authors.orderBy("name")
        if (typeof args.country === "string" || args.country === null) {
          authors.where("country", args.country)
        }
      },
    }),
  },
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request released
Projects
None yet
Development

No branches or pull requests

2 participants