In my previous post, I documented how “has many through” relationships weren’t natively supported in Ash framework, but could be implemented by using no_attributes? true. This option removes the need to have attributes matching to link two records, and instead uses a filter to determine which records are related to which, like this:

defmodule Example.World.Country do
  use Ash.Resource, # ...

  relationships do
    has_many :regions, Example.World.Region

    has_many :cities, Example.World.City do
      no_attributes? true
      filter expr(region.country_id == parent(id))
    end
  end
end

This lets you load cities that belongs to all regions of a specific country, like so:

iex(1)> Ash.get!(Example.World.Country, %{name: "Australia"}, load: [:cities])
%Example.World.Country{
  name: "Australia",
  cities: [
    %Example.World.City{name: "Melbourne", ...},
    ...
  ]
}

However, as of Ash v3.24.0, these kinds of relationships are now natively supported! We can simplify the code in the Country resource, to use the new through option on has_many:

defmodule Example.World.Country do
  use Ash.Resource, # ...

  relationships do
    has_many :regions, Example.World.Region

    has_many :cities, Example.World.City do
      no_attributes? true
      filter expr(region.country_id == parent(id))
    end
    
    has_many :cities, Example.World.City, through: [:regions, :cities]
  end
end

This works exactly the same way as the previous example, but is shorter and much easier to reason about!

You can still add filters and other options to restrict the list of returned records, eg.

has_many :largest_cities, Example.World.City do
  # this could also be `through [:cities]` to reuse the existing direct relationship
  through [:regions, :cities] 
  
  sort population: :desc
  limit 3
end

Thank you to Kenneth Kostrešević for implementing this feature! ❤️