Reading extra fields on many-to-many through resources

I have a resource Ride and a resource Person. They are many-to-many related through PersonRide, which has an extra field ‘seat’.

It’s possible to add a person to a ride with a seat by creating a PersonRide directly or by using manage_relationships with join_keys: [:seat]

I’d like to automatically load the seat when loading a ride with load: [:people]

I’m trying to add a calculation to the Person resource:

    calculate :seat, :atom, Calculations.Seat do
      argument :ride_id, :uuid
      public? true
    end

With a calculation like this:

    defmodule Calculations.Seat do
      def load(_query, _opts, _context) do
        [person_rides: [:seat, :ride_id]]
      end
    
      def calculate(records, _opts, %{arguments: %{ride_id: ride_id}}) do
        Enum.map(records, fn record ->
          Enum.find_value(record.person_rides, &(&1.ride_id == ride_id && &1.seat))
        end)
      end
    end

i can do

assert Ash.load!(person, seat: [ride_id: ride_id]).seat == :shotgun

(unsure though if i can use this to later auto-load the seats when reading a ride)

But this gets all the PersonRides for the Person and then filters them in Elixir.

Is there a way to do this more efficiently? Like altering the query in load/3 or somehow adding a filter to the load statement returned from load/3?

You can provide queries to relationships when loading them: load(seat: Seat |> Ash.Query.filter(...) |> Ash.Query.load(...))

1 Like

It seem like this works in the calculation

  def load(_query, _opts, %{arguments: %{ride_id: rid}}) do
    [person_rides: filter(PersonRide, ride_id == ^rid) |> load(:seat)]
  end

Now there only the PersonRides related to the ride and the person are loaded for the calculation.

this makes it possible to

Ash.load!(ride, [:available_seat_types, people: [seat: [ride_id: ride.id]]])

Thanks! Just found that here: How to apply field based sorting on many-to-many relationships? - #2 by zachdaniel