Querying with absinthe using two foreign keys to the same table

I have two tables, one in which I have two attributes that are foreign keys to the other table. Here my schema and migrations:

Schemas

schema "wire_types" do
    field(:name, :string)
    field(:description, string)
    has_many(:input_wires, Api.Run, foreign_key: :input_wire_id)
    has_many(:output_wires, Api.Run, foreign_key: :output_wire_id)

    timestamps()
 end
schema "runs" do
    field(:start_at, :naive_datetime)
    belongs_to(:input_wire, Api.WireType, foreign_key: :input_wire_id)
    belongs_to(:output_wire, Api.WireType, foreign_key: :output_wire_id)

    timestamps()
  end

Migrations

def up do
    create table(:wire_types) do
      add(:name, :string)
      add(:description, :string)
      
      timestamps()
    end  
end
def up do
    create table(:runs) do
      add(:input_wire_id, references(:wire_types, on_delete: :nothing))
      add(:output_wire_id, references(:wire_types, on_delete: :nothing))
      add(:start_at, :naive_datetime)

      timestamps()
    end
    
    create index(:runs, [:input_wire_id])
    create index(:runs, [:output_wire_id])
end

The question

My question is, how must I define my object types? in order to do the following query:

{
  wireTypes{
    name
    runs{
      id
    }
  }
}

Right now I get Ecto.QueryError because it is trying to find wiretype_id which does not exist in my runs table. How would you all suggest that I define this problem?
My current objects are as follows:

  object :run do
    field(:id, :id)
    field(:start_at, :naive_datetime)
    field(:input_wire, :wire_type, resolve: assoc(:input_wire))
    field(:output_wire, :wire_type, resolve: assoc(:output_wire))
  end
 object :wire_type do
    field(:id, :id)
    field(:name, :string)
    field(:description, :string)

    field(:runs, list_of(:run)) do
      resolve(&App.RunResolver.all/3)
    end
  end

I think this is also called a “composite foreign key” and as far as I know Ecto does not support it. So what you can then do is something like this:

field(:runs, list_of(:run)) do
  resolve(&resolve_runs/3)
end

...

defp resolve_runs(wire_type, _args, _resolution) do
  # Or even better: App.MyContext.list_runs_by_wire_type(wire_type)
  runs =
    from(r in App.Run, where: r.input_wire: wire_type.id, r.output_wire: wire_type.id) |> Repo.all()

  {:ok, runs}
end

You could even further optimize to prevent N+1 queries using batch/4.

(I’m not sure what App.RunResolver.all does exactly btw).

2 Likes