Packages

DSL for building JSON APIs fast. Creates endpoint views, renders linked data automatically.

Current section

Files

Jump to
carve lib batch.ex
Raw

lib/batch.ex

defmodule Carve.Batch do
@moduledoc """
Batched cache warm-up for views that define `cache_many`.
`warm/3` resolves the cached value for a set of entities of one view in a
single `__cache_many__/1` call, instead of one `__cache__/1` call per
entity. Views without `cache_many` fall back to the per-entity path,
preserving the existing `cache` behavior exactly.
"""
@doc """
Returns a map of entity id => cached value for every entity in `items`,
warming the request cache along the way.
For a view with `cache_many`, entities already present in the request
cache are excluded from the batch call; ids missing from the returned
map are cached as nil. Batching also works when caching is disabled
(`cache_key` is nil): the batch still runs once and the results are
returned directly.
"""
def warm(module, items, cache_key) when is_list(items) do
items =
items
|> Enum.reject(&(entity_id(&1) == nil))
|> Enum.uniq_by(&entity_id/1)
if batched?(module) do
warm_batched(module, items, cache_key)
else
Map.new(items, fn item ->
id = entity_id(item)
value =
Carve.Cache.fetch(cache_key, {module, :cache, id}, fn ->
module.__cache__(item)
end)
{id, value}
end)
end
end
@doc """
Whether the view module declares `cache_many`.
"""
def batched?(module) do
Code.ensure_loaded?(module) and function_exported?(module, :__cache_many__, 1)
end
@doc false
def entity_id(%{id: id}), do: id
def entity_id(%{"id" => id}), do: id
def entity_id(_), do: nil
defp warm_batched(module, items, cache_key) do
{hits, misses} =
Enum.reduce(items, {%{}, []}, fn item, {hits, misses} ->
id = entity_id(item)
case Carve.Cache.peek(cache_key, {module, :cache, id}) do
{:ok, value} -> {Map.put(hits, id, value), misses}
:error -> {hits, [item | misses]}
end
end)
case Enum.reverse(misses) do
[] ->
hits
misses ->
batch = module.__cache_many__(misses)
Enum.reduce(misses, hits, fn item, acc ->
id = entity_id(item)
value = Map.get(batch, id)
Carve.Cache.put(cache_key, {module, :cache, id}, value)
Map.put(acc, id, value)
end)
end
end
end