defmodule Ecto.Adapters.Mnesia.Constraint.ForeignKey do
@moduledoc """
Represents a foreignkey constraint
"""
alias Ecto.Adapters.Mnesia.Source
require Logger
defstruct name: nil, from: nil, to: nil, fields: [], errors: []
@type t :: %__MODULE__{
name: String.t() | nil,
from: Source.t() | nil,
to: Source.t() | nil,
fields: Keyword.t(),
errors: [term()]
}
@type opt() :: {:name, String.t()}
@type opts() :: [opt()]
@doc """
Returns newly created foreign key struct
"""
@spec new(Source.t(), atom(), opts()) :: t()
def new(from, rel, opts \\ [])
def new(%Source{} = from, rel, opts) when is_atom(rel) and is_list(opts) do
name =
from.schema.__schema__(:association, rel)
|> case do
nil ->
Keyword.fetch!(opts, :name)
%Ecto.Association.BelongsTo{owner_key: col} ->
Keyword.get_lazy(opts, :name, fn -> "#{from.table}_#{col}_fkey" end)
end
%__MODULE__{name: name, from: from}
|> add_assoc(from.schema.__schema__(:association, rel))
end
def new(_, _, _) do
%__MODULE__{errors: ["invalid arguments"]}
end
defp add_assoc(%{from: from, fields: fields} = c, %Ecto.Association.BelongsTo{} = a) do
to = Source.new(%{schema: a.related})
field =
{from.schema.__schema__(:field_source, a.owner_key),
to.schema.__schema__(:field_source, a.related_key)}
%{c | to: to, fields: [field | fields]}
end
defimpl Ecto.Adapters.Mnesia.Constraint.Proto do
alias Ecto.Adapters.Mnesia.Record
def check(c, from_params) do
c.fields
|> Enum.reduce([], fn {from, to}, acc ->
case Keyword.fetch(from_params, from) do
{:ok, nil} -> acc
{:ok, value} -> [{to, value} | acc]
:error -> acc
end
end)
|> case do
[] ->
:ok
to_params ->
pattern =
c.to.match_all
|> Record.update(to_params, c.to)
case :mnesia.match_object(c.to.table, pattern, :read) do
[] -> {:error, {:foreign_key, c.name}}
_ -> :ok
end
end
end
def table(c), do: c.from.table
end
end