How to write subqueries in absinthe?

Like this one?

{ 
  teachingCenters {
    id
    name
    email
    courses(search: "first") {
      id
      name
      cycle
      materials(limit: 5) {
        id
        quantity
        item {
          name
          description
          images {
            id
            altText
          }
        }
      }
    }
  } 
}

Is it supported? How resolver and it args will look like?

2 Likes

Hey! This is totally doable. The main thing to note is fundamentally,

{ 
  teachingCenters {
    id
    name
    email

works in exactly the same way as

    courses(search: "first") {
      id
      name
      cycle

When you have teachingCenters, you have the root query object, and you’re resolving a field for it, and you have a resolver.

When you do the courses field it’s the same idea, you’re just resolving off of the teaching_center object. The very naive version is basically:

object :teaching_center do
  field :id, :id
  # other fields
  field :courses, list_of(:course) do
    arg :search, :string
    resolve fn teaching_center, args, _ ->
      courses =
        teaching_center
        |> Ecto.assoc(:courses)
        |> where(^args)
        |> Repo.all
      {:ok, 
    end
  end
end

The idea is that when the query executor hits the courses field, it just gets the teaching center it’s under, and runs a query.

I called it naive because of course when you have a list of teaching centers, this produces N + 1 queries, which is very bad. Fortunately, Absinthe has a middleware pattern from which batching can be constructed, and the Absinthe.Ecto project provides some handy functions for doing ecto based batching. With that in mind we can do something like:

object :teaching_center do
  field :id, :id
  # other fields
  field :courses, list_of(:course) do
    arg :search, :string
    resolve assoc(:courses, fn courses_query, args, _ ->
      posts_query |> where(^args) # or whatever
    end)
  end
end

Now it’ll do just 1 SQL query to grab all the courses, with whatever arg processing you have.

To make it nest further, just add resolvers to each of the fields that you want to be able to load data from.

6 Likes

Fields are working in the same way and can receive resolver option with args. And where I should use batch function (looked at absinthe_ecto and recall tutorial) to avoid N+1, nice. Now I know how to address that, thank you!

1 Like