absinthe-graphql/dataloader

Allow access of parent values in query()

rjacobskind opened this issue · 5 comments

I'm using dataloader to resolve a field:

    field :foos, list_of(:foo) do
      resolve(dataloader(Foo, :foos, args: %{}))
    end

When I query this field, I eventually hit my custom query() function. I want to be able to explicitly access values from the parent so I can use them for certain clauses within my query, similar to what's shown below:

  def query(Foo, _params, %{bar: bar, baz: baz} = _foo_parent) do
    from(f in Foo,
      where: f.bar == ^bar,
      where: f.baz != ^baz
    )
  end

Is there a way to achieve this? If not, it would be helpful functionality to have added.

Hey @rjacobskind. I think there's a bit of a conceptual mismatch here, probably because the existing docs may not explain it super well.

You definitely don't want to access the parent inside of query, because if you make parent specific alterations to query, no batching would ever happen. Batching is possible because N things all end up with the same root query. If every child of N parents gets a different query, then you have N queries.

Can you make your situation a bit more concrete? You probably want to use run_batch but it'll be difficult for me to explain how without more details.

Ah I see. I've actually tried implementing a custom run_batch function, but haven't been able to get it working yet.

Essentially I'm trying to run a query with a join, where I need a value on the joined table to equal a value on the parent:

from(f in Foo,
   join: b in Baz,
   on: [foo_id: f.id],
   where: b.some_val == parent.some_val
)

Right, but the issue here is that you need to do this for all parents, not just one of them. Otherwise, you won't get batching. here's an example:

    field :foos, list_of(:foo) do
      resolve fn parent, _, %{context: %{loader: loader}} ->
        loader
        |> Dataloader.load(FooCtx, {:many, Foo}, for_bar: parent)
        |> on_load(fn loader ->
          {:ok, Dataloader.get(loader, FooCtx, {:many, Foo}, for_bar: parent.some_val)}
        end)
      end
    end

# in the FooCtx source
def run_batch(Foo, query, :for_bar, bar_values, repo_opts) do
  query = from(f in Foo,
   join: b in Baz,
   on: [foo_id: f.id],
   where: b.some_val in ^bar_values,
   select: {b.some_val, f}
  )

  results =
    query
    |> Repo.all(repo_opts)
    |> Enum.group_by(fn {value, _} -> value end, fn {_, foo} -> foo end)

  for value <- bar_values, do: Map.get(results, value, [])
end

Yes, you're right -- Thanks for clarifying that. Using a custom run_batch was the solution I was looking for.

Paryz commented

@benwilson512 maybe there is a chance to write about it in some docs? I've just spent around 3 days figuring out why pushing the parent to the args of dataloader produced so strange results, but after reading your explanation it finally got clear 😅