In my phoenix application, I have a many to many relationship between an Artist and a Cause schema implemented using a join table artists_causes. In my Artists schema, I have many_to_many :causes, Cause, join_through: "artists_causes" and in the causes schema I have many_to_many :artists, Artist, join_through: "artists_causes"
I am using absinthe for graphql and in my CauseTypes module, I have implemented a the cause object as below
```
defmodule MyAppWeb.Schema.CauseTypes do
@moduledoc """
All types for causes
"""
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1, dataloader: 3]
object :cause do
field :id, :id
field :artists, list_of(:artist), resolve: dataloader(Artists)
end
def dataloader do
alias MyApp.{Artists, Causes}
loader = Dataloader.new
|> Dataloader.add_source(Causes, Causes.datasource())
|> Dataloader.add_source(Artists, Artists.datasource())
end
def context(ctx) do
Map.put(ctx, :loader, dataloader())
end
def plugins do
[Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults()
end
end```
From my understanding, with Absinthe Dataloader, the dataloader/1 is what I need to have the list of Artists loaded. However, I am not able to query for the artists from within a cause getting the error artists: #Ecto.Association.NotLoaded<association :artists is not loaded> when I run the query below in graphiql
```
query{
causes{
id
artists {
id
}
}
}```
Is there any little piece that I am missing on working with many to many relationships?
I’ve not yet encountered many_to_many relationships in combination with dataloader but that’s also because it generally makes more sense to use a has_many relationship with through set, and make the join table a separate schema.
If at a later stage you want to add columns to the join table it is much easier to add.
many_to_many and has_many through: are not the same thing and depending on your use case one or the other should be used.
One example is:
In fact, given :through associations are read-only,
many_to_many also allows you to create a join schema.
:join_through - specifies the source of the associated data. It may be a string, like "posts_tags", representing the underlying storage table or an atom, like MyApp.PostTag, representing a schema. This option is required.
Hi @hlx@maartenvanvliet Thanks for comments, First mistake I made before I asked the first question above was that I had not preloaded my Artist schema in the query list_causes() method in Causes context, I updated my list_causes() function from
def list_causes do
Repo.all(MyApp.Causes.Cause)
end
to
def list_causes do
Repo.all(from c in Cause,
left_join: ac in "artists_causes", on: c.id == ac.cause_id,
left_join: a in Artist, on: a.id == ac.artist_id,
preload: [:artists]
)
end
and got past the artists: #Ecto.Association.NotLoaded<association :artists is not loaded> problem. However, I am now getting the error FunctionClauseError at POST /graphiql\n\nException:\n\n ** (FunctionClauseError) no function clause matching in anonymous fn/3 in Absinthe.Resolution.Helpers.dataloader/1 This message maybe pointing towards something with the Absinthe.Resolution.Helpers.dataloader/1 method but it does not make sense to me as I have the helpers imported @hlx please see below my Cause context
defmodule MyApp.Causes do
import Ecto.Query, warn: false
alias SingForNeeds.Repo
alias MyApp.Causes.Cause
alias MyApp.Artists.Artist
def list_causes do
Repo.all(from c in Cause,
left_join: ac in "artists_causes", on: c.id == ac.cause_id,
left_join: a in Artist, on: a.id == ac.artist_id,
preload: [:artists]
)
end
Is there something else I could be missing. I will respond to you guys incase I make any progress.
I think it’s better if I setup a small example for you (tomorrow). In the mean time I suggest you read up on dataloader and when and why you should use it (the docs are better than my explanation).
Thanks @hlx for the simplified query, I however want to do a left join so it can load causes with no artists as well atleast as artists:[], and hence I did update yours to ‘from c in Cause, left_join: a in assoc(c, :artists), preload: [:artists]. My query returns [] if I do inner join and the same error above when I do left join # FunctionClauseError at POST /graphiql\n\nException:\n\n ** (FunctionClauseError) no function clause matching in anonymous fn/3 in Absinthe.Resolution.Helpers.dataloader/1\n
Does your query run with left join as well?
Also, do you have a link where they are implementing the dataloader for M to M relationships? That would be helpful Unfortunately I havent been able to find good examples in this area. Thanks again!
I still get the same error for left join (FunctionClauseError) no function clause matching in anonymous fn/3 in Absinthe.Resolution.Helpers.dataloader/1\n \n The following arguments were given to anonymous fn/3 and not with a right join.