Idiomatic way to expose a two-step join through Absinthe

I have roughly the following model:

People, can make Recommendations, about Things that are in Categories.

Each has an Absinthe schema. I would like to add a field to People that lets a user query how many Things that person has Recommended in a Category, so:

query {
  person(id: "id") {
    name
    categories {
      name
      numberOfRecommendations # computed, not a real field
    }
  }
}

This is straightforward to build if categories is one table away, I just do a resolve dataloader(:categories) on the Person schema, and a run_batch override on the Category schema to define the custom aggregation of numberOfRecommendations. However, since I’m two tables away here, I’m in effect aggregating twice, as I want the number of all recommendations, across all Things within a Category

I’m currently stringing this together by overriding run_batch and building a query that does the necessary joins underneath and then doing the relevant count in another run_batch on the Categories context. I think I can get it to work, but it feels… kludgey. Is there a more idiomatic way to do this?

In your Person schema, I’m guessing you have has_many(:categories, Category)

and in Category, I’m guessing you have has_many(:recommendations, Recommendation)

In Person, you’ll want to add a has_many(:recommendations, through: [:categories, :recommendations])

Then you can dataloader(:recommendations). Or since you want a count

resolve(fn parent, _args, %{context: %{loader: loader}} ->
        Dataloader.load(loader, :my_source, :recommendations, parent)
        |> on_load(fn loader ->
            recommendations = Dataloader.get(loader, :my_source, :recommendations, parent)
            {:ok, length(recommendations)}
        end)
      end)

There’s probably a good way to optimize from there so you’re pulling counts instead of pulling all the recommendations data.

2 Likes

In addition to @bitcapulet’s solution, you could simply use the batch helper.

I’ve seen many people try to fit dataloader into awkward places when it’s not only not necessary, but there is a better way.

2 Likes