Skip to content

Instantly share code, notes, and snippets.

@dplummer
Created October 5, 2016 17:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dplummer/e1d21a26c370f8fc4a14b02ab034cd79 to your computer and use it in GitHub Desktop.
Save dplummer/e1d21a26c370f8fc4a14b02ab034cd79 to your computer and use it in GitHub Desktop.
recursive batch resolver for Absinthe
defmodule Grav.Resolver do
def resolve(protocol_module, args, field) do
{:ok, %{meta: meta} = root} = apply(protocol_module, :where, [args])
records_name = root |> Map.keys |> Enum.reject(& &1 in [:meta, :links, :linked]) |> hd
records = preload_associations(root[records_name], field |> derive_preloads)
{:ok, root |> Map.put(records_name, records) |> Map.merge(meta)}
end
@spec derive_preloads(Absinthe.Execution.Field.t) :: [atom]
def derive_preloads(%Absinthe.Execution.Field{ast_node: ast_node}), do: derive_preloads(ast_node)
@spec derive_preloads(Absinthe.Language.Field.t) :: [atom]
def derive_preloads(%Absinthe.Language.Field{selection_set: selection_set}), do: derive_preloads(selection_set)
@spec derive_preloads(Absinthe.Language.SelectionSet.t) :: [atom]
def derive_preloads(%Absinthe.Language.SelectionSet{selections: selections}), do: derive_preloads(selections)
@spec derive_preloads([Absinthe.Language.Field.t]) :: [atom]
def derive_preloads(selections) when is_list(selections) do
Enum.reduce(selections, [], fn(field, acc) -> acc ++ derive_preload(field.name, field) end)
end
def derive_preloads(nil), do: []
def derive_preload(field_name, field) do
case Inflex.pluralize(field_name) do
^field_name -> derive_preloads(field)
_ ->
case find_client(field_name |> Macro.camelize) do
nil -> []
_ -> [{field_name |> String.to_atom, derive_preloads(field)}]
end
end
end
def preload_associations(records, []), do: records
def preload_associations(records, [{assoc, deps} | tail]) do
name = assoc |> Atom.to_string
foreign_key = "#{name}_id" |> String.to_atom
pluralized = name |> Inflex.pluralize |> String.to_atom
case records |> hd |> Map.fetch(assoc) do
{:ok, _} -> inlined(records, assoc, deps, name, foreign_key, pluralized)
_ -> external(records, assoc, deps, name, foreign_key, pluralized)
end
|> preload_associations(tail)
end
def inlined(records, assoc, deps, name, foreign_key, pluralized) do
associated_records = records
|> Enum.map(& Map.fetch!(&1, assoc))
|> preload_associations(deps)
case associated_records |> hd do
%{} ->
records
|> Enum.map(fn record ->
associated_record = associated_records |> Enum.find(& &1.id == Map.get(record, assoc).id)
record |> Map.put(assoc, associated_record)
end)
_ -> records
end
end
def external(records, assoc, deps, name, foreign_key, pluralized) do
ids = records |> Enum.map(& Map.get(&1, foreign_key))
client_module = find_client(name |> Macro.camelize()) |> String.to_atom()
{:ok, %{^pluralized => associated_records}} = apply(client_module, :where, [ %{ids: ids |> Enum.uniq |> Enum.join(",")} ])
associated_records = associated_records |> preload_associations(deps)
records
|> Enum.map(fn record ->
associated_record = associated_records |> Enum.find(& &1.id == Map.get(record, foreign_key))
record |> Map.put(assoc, associated_record)
end)
end
def find_client(record_type) do
[
AccountClient,
GnomonClient,
LedgerClient,
QuasiClient,
SolicitorClient,
]
|> Enum.map(fn client_module -> "#{client_module}.#{record_type}" end)
|> Enum.find(fn potential_name ->
case Code.ensure_loaded(potential_name |> String.to_atom()) do
{:error, _} ->
false
_ ->
true
end
end)
end
end
@dplummer
Copy link
Author

dplummer commented Oct 5, 2016

FYI this code was written for a hackathon. The find_client function in particular is a total hack.

@dplummer
Copy link
Author

dplummer commented Oct 5, 2016

Used in the Absinthe schema field block like resolve &Grav.Resolver.resolve(AccountClient.User, &1, &2)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment