Current section
Files
Jump to
Current section
Files
lib/scrypath/hydration.ex
defmodule Scrypath.Hydration do
@moduledoc false
import Ecto.Query
alias Scrypath.Telemetry
@spec hydrate(module(), [map()], keyword()) :: {[struct()], [term()]}
def hydrate(_schema_module, [], _opts), do: {[], []}
def hydrate(schema_module, hits, opts) when is_list(hits) do
repo = Keyword.fetch!(opts, :repo)
preload = Keyword.get(opts, :preload, [])
source_id_field = source_id_field(schema_module)
source_ids = Enum.map(hits, &fetch_hit_id!(&1, source_id_field))
metadata = %{
schema: schema_module,
repo: repo,
hit_count: length(hits)
}
Telemetry.span([:scrypath, :hydration], metadata, fn ->
query =
schema_module
|> where([record], field(record, ^source_id_field) in ^source_ids)
|> maybe_preload(preload)
records = repo.all(query)
records_by_id =
Map.new(records, fn record ->
{Map.fetch!(record, source_id_field), record}
end)
{ordered_records, missing_ids} =
Enum.reduce(source_ids, {[], []}, fn source_id, {ordered_records, missing_ids} ->
case Map.fetch(records_by_id, source_id) do
{:ok, record} -> {[record | ordered_records], missing_ids}
:error -> {ordered_records, [source_id | missing_ids]}
end
end)
|> then(fn {records, missing_ids} ->
{Enum.reverse(records), Enum.reverse(missing_ids)}
end)
{{ordered_records, missing_ids},
%{
record_count: length(ordered_records),
missing_count: length(missing_ids)
}}
end)
end
defp source_id_field(schema_module) do
if function_exported?(schema_module, :__schema__, 1) do
case schema_module.__schema__(:primary_key) do
[field | _] -> field
_ -> Scrypath.document_id_field(schema_module)
end
else
Scrypath.document_id_field(schema_module)
end
end
defp fetch_hit_id!(hit, source_id_field) when is_map(hit) do
cond do
Map.has_key?(hit, source_id_field) ->
Map.fetch!(hit, source_id_field)
Map.has_key?(hit, Atom.to_string(source_id_field)) ->
Map.fetch!(hit, Atom.to_string(source_id_field))
true ->
raise ArgumentError,
"search hit is missing hydration id #{inspect(source_id_field)}"
end
end
defp maybe_preload(query, []), do: query
defp maybe_preload(query, preload), do: preload(query, ^preload)
end