Multiple has_many through associations with queryable chasing
Closed this issue · 2 comments
Versions
Ecto: 2.2.11
Dataloader 1.0.4
Overview
In our system, we have the following contexts:
User:
has_many :institution_users, InstitutionUser
has_many :institutions, through: [:institution_users, :institution]
has_many :institution_spaces, through: [:institutions, :spaces]
Institution:
has_many :spaces, Space
has_many :institution_users, InstitutionUser
has_many :users, through: [:institution_users, :user]
Our goal is to serialize the institution_spaces
as part of our user
object in the schema:
object :user do
field :name, :string
field :institution_spaces, list_of(:institution_space) do
resolve dataloader Dataloaders.User, fn _, _, res ->
{ :institution_spaces, %{user_id: Util.Resolution.user_id(res)} }
end
end
The associations work correctly via raw Ecto when preloading.
Error
Serializing it via the schema results in:
** (CaseClauseError) no case clause matching: %Ecto.Association.HasThrough{cardinality: :many, field: :institutions, on_cast: nil, owner: Api.Users.User, owner_key: :id, relationship: :child, through: [:institution_users, :institution], unique: true}
stemming from the stack trace of:
stacktrace:
(dataloader) lib/dataloader/ecto.ex:327: Dataloader.Source.Dataloader.Ecto.chase_down_queryable/2
(dataloader) lib/dataloader/ecto.ex:337: Dataloader.Source.Dataloader.Ecto.get_keys/2
(dataloader) lib/dataloader/ecto.ex:247: Dataloader.Source.Dataloader.Ecto.fetch/3
(dataloader) lib/dataloader/ecto.ex:287: Dataloader.Source.Dataloader.Ecto.load/3
(elixir) lib/enum.ex:1899: Enum."-reduce/3-lists^foldl/2-0-"/3
(dataloader) lib/dataloader.ex:123: Dataloader.load_many/4
(absinthe) lib/absinthe/resolution/helpers.ex:255: Absinthe.Resolution.Helpers.do_dataloader/6
(absinthe) lib/absinthe/resolution.ex:209: Absinthe.Resolution.call/2
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:209: Absinthe.Phase.Document.Execution.Resolution.reduce_resolution/1
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:168: Absinthe.Phase.Document.Execution.Resolution.do_resolve_field/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:257: Absinthe.Phase.Document.Execution.Resolution.build_result/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:53: Absinthe.Phase.Document.Execution.Resolution.perform_resolution/3
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:24: Absinthe.Phase.Document.Execution.Resolution.resolve_current/3
(absinthe) lib/absinthe/pipeline.ex:269: Absinthe.Pipeline.run_phase/3
(absinthe_plug) lib/absinthe/plug.ex:414: Absinthe.Plug.run_query/4
(absinthe_plug) lib/absinthe/plug.ex:240: Absinthe.Plug.call/2
Notes
It looks like it goes through https://github.com/absinthe-graphql/dataloader/blob/master/lib/dataloader/ecto.ex#L310 correctly and matches the HasThrough
in the case. Then, it runs again at https://github.com/absinthe-graphql/dataloader/blob/master/lib/dataloader/ecto.ex#L326. Based on how our associations are setup, this returns an additional HasThrough
struct which then results in the error.
Experiencing the same behavior with a plain resolver.
object :shelf do
field :id, :id
field :name, :string
field :products, list_of(:product), resolve: dataloader(Product) // Also tried with "Project" in place of "Product", same result
end
Where the Shelf
object is associated to products like so:
defmodule Shelf do
belongs_to(:position_taxon, Taxon)
has_many(:classifications, through: [:position_taxon, :classifications])
has_many(:products, through: [:classifications, :product])
I get
[error] #PID<0.1860.0> running MasterProxy.Plug (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: POST /api/platform/merchandising/
** (exit) an exception was raised:
** (CaseClauseError) no case clause matching: %Ecto.Association.HasThrough{cardinality: :many, field: :classifications, on_cast: nil, owner: Shelf, owner_key: :position_taxon_id, relationship: :child, through: [:position_taxon, :classifications], unique: true}
(dataloader) lib/dataloader/ecto.ex:327: Dataloader.Source.Dataloader.Ecto.chase_down_queryable/2
(dataloader) lib/dataloader/ecto.ex:337: Dataloader.Source.Dataloader.Ecto.get_keys/2
(dataloader) lib/dataloader/ecto.ex:247: Dataloader.Source.Dataloader.Ecto.fetch/3
(dataloader) lib/dataloader/ecto.ex:287: Dataloader.Source.Dataloader.Ecto.load/3
(elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
(dataloader) lib/dataloader.ex:123: Dataloader.load_many/4
(absinthe) lib/absinthe/resolution/helpers.ex:255: Absinthe.Resolution.Helpers.do_dataloader/6
(absinthe) lib/absinthe/resolution.ex:209: Absinthe.Resolution.call/2
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:209: Absinthe.Phase.Document.Execution.Resolution.reduce_resolution/1
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:168: Absinthe.Phase.Document.Execution.Resolution.do_resolve_field/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:257: Absinthe.Phase.Document.Execution.Resolution.build_result/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:153: Absinthe.Phase.Document.Execution.Resolution.do_resolve_fields/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:72: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:98: Absinthe.Phase.Document.Execution.Resolution.walk_results/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:87: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:257: Absinthe.Phase.Document.Execution.Resolution.build_result/4
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:98: Absinthe.Phase.Document.Execution.Resolution.walk_results/6
(absinthe) lib/absinthe/phase/document/execution/resolution.ex:77: Absinthe.Phase.Document.Execution.Resolution.walk_result/5
Fixed by #65