kylevsteger

kylevsteger

Dataloader on Join Table Association

Hey all! I’m having trouble with Absinthe and Dataloader when trying to resolve friends of a user.

I was able to load the friends without much issue, but now I’m trying to add a status to the response, which lives on the join table.

The last chunk in the code below is where I’m struggling. Any insight is much appreciated! Thanks!

# User Ecto Schema
@primary_key {:id, :binary_id, autogenerate: true}
schema "users" do
  ...
  has_many :friends_assoc, Friend, foreign_key: :user_id
  has_many :reverse_friends_assoc, Friend, foreign_key: :friend_id
  has_many :friends, through: [:friends_assoc, :friend]
  has_many :reverse_friends, through: [:reverse_friends_assoc, :user]
end

# Friend Ecto Schema
@primary_key {:id, :binary_id, autogenerate: true}
schema "friends" do
  field :status, FriendStatus
  belongs_to :user, User, type: :binary_id
  belongs_to :friend, User, type: :binary_id
end
# User Absinthe Schema
object :user do
  field :id, non_null(:string)
  ...
  field :friends, list_of(:friend) do
    arg :status, :friend_status
    # This ultimately points to: `Dataloader.Ecto.new(Repo, query: fn _, args -> generate_query(args) end)`
    resolve dataloader(User)
  end
end

# Friend Absinthe Schema
object :friend do
  field :id, :string
  field :first_name, :string
  field :last_name, :string
  # This comes back as `null` on the GraphQL response
  # because it's not on the Ecto User schema 
  # but I'm not sure how to remedy the situation
  field :status, :friend_status
end

UPDATE

I wound up going with a custom Dataloader.KV that points to this function

@doc """
Queries the database for Users and loads their friends.
"""
@spec get_friends_with_status([Ecto.UUID.t()], map()) :: map()
def get_friends_with_status(ids, %{status: status} = _args) do
  query = from u in User,
    left_join: fa in assoc(u, :friends_assoc),
    left_join: fr in assoc(fa, :friend),
    where: u.id in ^ids,
    where: fa.status == ^status,
    select: %{
      user_id: u.id,
      friend: %{
        first_name: fr.first_name,
        last_name: fr.last_name,
        email: fr.email,
        id: fr.id,
        status: fa.status
      }
    }

  id_map = Map.new(ids, & {&1, []})

  query
  |> Repo.all()
  |> Enum.reduce(id_map, fn %{user_id: user_id, friend: friend}, acc ->
    Map.update(acc, user_id, [friend], fn
      friends -> [friend | friends]
    end)
  end)
end

Marked As Solved

fuelen

fuelen

Hello!
Personally I would go with another graphql schema.
:friend object is context-specific. It has an id of the user, but different status depending on parent record. This is error-prone, because GraphQL client on frontend side MAY use simple caching by id field only, so other users may borrow friends.
To avoid this you have to use an id from the friends table. But it would be simpler to expose :user association:

object :user do
  field :id, non_null(:string)
  ...
  field :friends, list_of(:friend) do
    arg :status, :friend_status
    # resolve using :friends_assoc
  end
end

object :friend do
  field :id, non_null(:string)
  field :status, :friend_status
  field :user, :user
  field :friend, :user
end

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
vac
Hi, I’m quite new in Elixir and I’m trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and I...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: <h1>Create Post</h1> <%= ...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New

We're in Beta

About us Mission Statement