Dynamic keys in :on for joining in Ecto

I’m trying to build out a function that, if desired, I can build a list of joins in Ecto when I want to use preload to save trips to the database. If I know ahead of time what the joining keys will be and they’re all the same, the following works just fine:

def join_for_preload(params, base_query, preloads), do: {params, build_joins(query, preloads)}

defp build_joins(query, []), do: query
defp build_joins(query, [{nested_query, nested_preloads} | preloads] do
  nested_queryable = query.__schema__(:association, nested_query).queryable
  sub_query = build_joins(nested_queryable, nested_preloads)
  query
  |> join(:left, [q], s in subquery(sub_query), on: [id: q.known_id])
  |> build_joins(preloads)

defp build_joins(query, [preload | preload]) do
  query
  |> join(:left, [q], a in assoc(q, ^preload)
  |> build_joins(preloads)

Where preloads takes on the structure of the :preload option e.g. [parent: [child: :field]] or [:field1, :field2, etc...]

This causes problems when I don’t know what the parent field is called, so I’d like to alter the function that handles nested associations to do something like this:

defp build_joins(query, [{nested_query, nested_preloads} | preloads] do
  nested_query_struct = query.__schema__(:association, nested_query)
  [nested_queryable, nested_owner_key] = [nested_query_struct.queryable, nested_query_struct.owner_key]
  sub_query = build_joins(nested_queryable, nested_preloads)
  query
  |> join(:left, [q], s in subquery(sub_query), on: [id: q[^nested_owner_key]]) # <-- Use a dynamic key value here
  |> build_joins(preloads)

Questions:
Is this possible with Ecto or a macro?
Is it worth the effort? Or should I just write the joins if/when I need them?

You can use field/2.

join on: field(q, ^key)

2 Likes

Thank you! I thought field could only be used in Schema definitions for some reason so I disregarded it immediately. That did the trick though! I’ll have to update the recursion to finish what I want to do, but that took a large pain point away.

Oh yeah they are two different things but have the same name (but different arity). In the hexdocs under Ecto.Query.API there are a lot of useful things that can be used.

1 Like