Aggregate fields in Absinthe

I ended up using Dataloader.KV and it worked great.

Here’s my query:

{
  applications(ids: [1, 2]) {
    id
    review {
      total_count
      assigned_count
      submitted_count
    }
  }
}

Object schemas:

defmodule Document.Schema.Application do
  use Absinthe.Schema.Notation

  import Absinthe.Resolution.Helpers, only: [dataloader: 1]

  object :application do
    field :id, :id
    field :review, :review, resolve: dataloader(:review)
  end
end

defmodule Document.Schema.Review do
  use Absinthe.Schema.Notation

  object :review do
    field :total_count,     :integer
    field :assigned_count,  :integer
    field :submitted_count, :integer
  end
end

And dataloader:

defmodule Document.Dataloader.Review do

  def new do
    Dataloader.KV.new(&load/2)
  end

  def load(_batch_key, applications) do
    user_ids = Enum.map(applications, &(&1.user_id))

    total_counts = total_counts(user_ids)
    assigned_counts = started_counts(user_ids)
    submitted_counts = submitted_counts(user_ids)

    Enum.reduce applications, %{}, fn application, acc ->
      Map.put acc, application, %{
        total_count: total_counts[application.user_id] || 0,
        assigned_count: assigned_counts[application.user_id] || 0,
        submitted_count: submitted_counts[application.user_id] || 0
      }
    end
  end

  defp total_counts(user_ids) do
    import Ecto.Query

    Schema.Reviewer.Review
      |> select([r], {r.reviewer_user_id, count(r.id)})
      |> where([r], r.reviewer_user_id in ^user_ids)
      |> group_by([r], r.reviewer_user_id)
      |> Datastores.Shard.all
      |> Map.new
  end

  defp started_counts(user_ids) do
    import Ecto.Query

    Schema.Reviewer.Review
      |> select([r], {r.reviewer_user_id, count(r.id)})
      |> where([r], r.reviewer_user_id in ^user_ids)
      |> where([r], r.state == 2)
      |> group_by([r], r.reviewer_user_id)
      |> Datastores.Shard.all
      |> Map.new
  end

  defp submitted_counts(user_ids) do
    import Ecto.Query

    Schema.Reviewer.Review
      |> select([r], {r.reviewer_user_id, count(r.id)})
      |> join(:inner, [r], a in Schema.Application, r.reviewer_application_id == a.id)
      |> join(:inner, [r, a], c in Schema.Category, a.category_id == c.id)
      |> where([r, a, c], r.reviewer_user_id in ^user_ids)
      |> where([r, a, c], c.state == "submitted")
      |> group_by([r], r.reviewer_user_id)
      |> Datastores.Shard.all
      |> Map.new
  end

end

Works great, no n+1 queries!

P.S. A little different from the distilled down example from previous posts, but this is actual working code!

10 Likes