Packages
double_down
0.57.0
0.69.0
0.68.0
0.66.0
0.65.0
0.64.1
0.64.0
0.63.3
0.63.2
0.63.1
0.63.0
0.62.1
0.61.0
0.60.4
0.60.3
0.60.2
0.60.1
0.60.0
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.0
0.54.0
0.53.0
0.52.3
0.52.2
0.52.1
0.52.0
0.51.0
0.50.1
0.50.0
0.49.0
0.48.1
0.48.0
0.47.2
0.47.1
0.47.0
0.46.3
0.46.2
0.46.1
0.46.0
0.45.0
0.44.0
0.43.0
0.42.0
0.41.1
0.41.0
0.40.0
0.39.0
0.38.0
0.37.2
0.37.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.1
0.31.0
0.30.1
0.30.0
0.29.0
0.28.1
0.28.0
0.27.0
0.26.0
0.24.0
Builds on the Mox pattern — generates behaviours and dispatch facades from `defcallback` declarations — and adds stateful test doubles powerful enough to test Ecto.Repo operations without a database.
Current section
Files
Jump to
Current section
Files
lib/double_down/repo/impl/ecto_parity.ex
if Code.ensure_loaded?(Ecto) do
defmodule DoubleDown.Repo.Impl.EctoParity do
@moduledoc false
# Functions that make the in-memory Repo fakes behave more like
# real Ecto.Repo by inspecting Ecto schema metadata.
#
# Kept separate from InMemoryShared (which handles store mechanics)
# so schema-introspection concerns are isolated and reusable.
# -------------------------------------------------------------------
# FK backfill
# -------------------------------------------------------------------
@doc false
@spec backfill_foreign_keys(struct(), term(), (struct(), atom(), term() -> term())) ::
{struct(), term()}
def backfill_foreign_keys(%{__struct__: schema} = record, store, insert_fn) do
if function_exported?(schema, :__schema__, 1) do
schema.__schema__(:associations)
|> Enum.reduce({record, store}, fn assoc_name, {acc, st} ->
case schema.__schema__(:association, assoc_name) do
%Ecto.Association.BelongsTo{
field: field,
owner_key: fk_field,
related_key: pk_field
} ->
backfill_belongs_to(acc, st, field, fk_field, pk_field, insert_fn)
_other ->
{acc, st}
end
end)
else
{record, store}
end
end
defp backfill_belongs_to(record, store, assoc_field, fk_field, pk_field, insert_fn) do
assoc_value = Map.get(record, assoc_field)
fk_value = Map.get(record, fk_field)
case {assoc_value, fk_value} do
{%Ecto.Association.NotLoaded{}, _} ->
# Association not loaded — leave as-is
{record, store}
{%{__struct__: _} = parent, nil} ->
pk_value = Map.get(parent, pk_field)
if pk_value do
# Parent already has a PK — just copy it
{Map.put(record, fk_field, pk_value), store}
else
# Parent PK is nil — insert it to trigger autogeneration,
# then use the generated PK (matching Ecto's behaviour of
# recursively inserting belongs_to parents)
case insert_fn.(parent, :insert, store) do
{{:ok, inserted_parent}, new_store} ->
record =
record
|> Map.put(fk_field, Map.get(inserted_parent, pk_field))
|> Map.put(assoc_field, inserted_parent)
{record, new_store}
_error ->
# Parent insert failed — leave as-is
{record, store}
end
end
_ ->
# FK already set, or no association value — leave as-is
{record, store}
end
end
# -------------------------------------------------------------------
# Association reset
# -------------------------------------------------------------------
@doc false
@spec reset_associations(struct()) :: struct()
def reset_associations(%{__struct__: schema} = record) do
if function_exported?(schema, :__schema__, 1) do
schema.__schema__(:associations)
|> Enum.reduce(record, fn assoc_name, acc ->
assoc = schema.__schema__(:association, assoc_name)
Map.put(acc, assoc.field, %Ecto.Association.NotLoaded{
__field__: assoc.field,
__owner__: schema,
__cardinality__: assoc.cardinality
})
end)
else
record
end
end
end
end