Ash FrameworkAF
Ash Framework3y ago
10 replies
skander

Manual relationship with recursive CTE

I'm trying to implement this comment/thread (Fetching descendants in calculation) and creating a manual relationship with a recursive CTE (for a tag in a graph structure, get all parent tags). I'm able to get it done with Ecto spitting out grouped map lists. I'm getting a bit stuck figuring out how to get them as an Ash Resource in the end.

Is there anything major I'm missing?

  use Ash.Resource.ManualRelationship
  require Ash.Query
  require Ecto.Query

  def load(records, _opts, %{query: _query, actor: _actor, authorize?: _authorize?}) do
    tag_ids = Enum.map(records, & &1.id)

    initial_query =
      Tag
      |> Ecto.Query.select_merge([t], %{original_tag_id: t.id})
      |> Ecto.Query.where([t], t.id in ^tag_ids)

    recursion_query =
      Tag
      |> Ecto.Query.join(:inner, [t], pt in "parent_tags", on: pt.parent_tag_id == t.id)
      |> Ecto.Query.select_merge([t, pt], %{original_tag_id: pt.original_tag_id})

    parent_tags_query = initial_query |> Ecto.Query.union(^recursion_query)
    
    tag_attributes = Tag |> Ash.Resource.Info.attributes() |> Enum.map(& &1.name)
    select_fields = [:original_tag_id | tag_attributes]

    results =
      Ecto.Query.from("parent_tags")
      |> Ecto.Query.select(^select_fields)
      |> Ecto.Query.recursive_ctes(true)
      |> Ecto.Query.with_cte("parent_tags", as: ^parent_tags_query)
      |> Repo.all()

    {:ok,
     results
     # Group by original tag id and drop from final value
     |> Enum.group_by(& &1.original_tag_id, &Map.drop(&1, [:original_tag_id]))}
  end


Results look like:
%{
  id: <<115, 49, 103, 214, 71, 206, 68, 225, 173, 194, 151, 80, 189, 122, 121,
    237>>,
  name: "Bikes",
  parent_tag_id: <<35, 126, 25, 129, 171, 216, 76, 141, 185, 212, 147, 171, 74,
    59, 48, 174>>,
}
Was this page helpful?