# `Ramal.Overlay`
[🔗](https://github.com/agustinrojas1/ramal/blob/v0.2.0/lib/ramal/overlay.ex#L1)

The overlay: what a branch changed, as a plain map.

Keys are `{resource, key}` pairs, called refs here, where `key` is the row's primary key. Values are
`{:created, record}`, `{:updated, record}` or `:deleted`. The record is whatever the caller stores, normally the
full Ash struct as the action left it.

Everything in this module is a pure function over maps and lists. It never calls Ash, so it is tested with plain
maps. The pieces that need Ash (running the filter, sorting by Ash expressions, listing attributes) arrive as
functions from the caller.

## How entries combine

The overlay only records the net effect on the base, so putting a second entry for a ref follows these rules:

| existing | `put_created` | `put_updated` | `put_deleted` |
|---|---|---|---|
| none | `:created` | `:updated` | `:deleted` |
| `:created` | `:created` (replaced) | `:created` (record replaced) | no entry: the base never had the row |
| `:updated` | `:updated` | `:updated` | `:deleted` |
| `:deleted` | `:updated`: the base row is replaced | `:updated` | `:deleted` |

A `:deleted` entry therefore always means the base had the row. Rejecting a duplicate primary key or an update of
a missing row is the write layer's job (the data layer would do it on the real base); the overlay does not police it.

## Example

    iex> ref = {MyApp.Herd, "R7"}
    iex> overlay = Ramal.Overlay.new() |> Ramal.Overlay.put_updated(ref, %{id: "R7", name: "R7b"})
    iex> Ramal.Overlay.slice(overlay, MyApp.Herd)
    %{"R7" => {:updated, %{id: "R7", name: "R7b"}}}

# `entry`

```elixir
@type entry() :: {:created, record()} | {:updated, record()} | :deleted
```

# `key`

```elixir
@type key() :: term()
```

# `record`

```elixir
@type record() :: map()
```

# `ref`

```elixir
@type ref() :: {resource(), key()}
```

# `resource`

```elixir
@type resource() :: module()
```

# `slice`

```elixir
@type slice() :: %{optional(key()) =&gt; entry()}
```

# `t`

```elixir
@type t() :: %{optional(ref()) =&gt; entry()}
```

# `diff`

```elixir
@spec diff(t(), %{optional(ref()) =&gt; record()}, keyword()) ::
  {:ok, [Ramal.Change.t()]} | {:error, Ramal.Error.t()}
```

The overlay as a list of `Ramal.Change` structs, computed against the base rows as the branch first saw them.

`base` maps each ref the branch touched on an existing row to that row's snapshot. It never queries anything: the
snapshot is what the branch kept at first touch. Created rows need no snapshot. Rows whose updates netted out to
no difference are left out.

The result is sorted by ref so it is deterministic. The overlay is a map and does not remember the order of writes;
`Ramal.diff/1` orders changes by the branch log.

Returns `{:error, %Ramal.Error{reason: :missing_snapshot}}` when an updated or deleted entry has no snapshot, which
means the write layer forgot to record one.

## Options

  * `:attributes` - a function from a record to a `%{atom => value}` map. Defaults to the record's own keys without
    the `__meta__`-style ones. The Ash-aware caller passes one built from the resource's attributes.

## Example

    iex> overlay = %{{MyApp.Herd, 1} => {:updated, %{id: 1, name: "b"}}}
    iex> base = %{{MyApp.Herd, 1} => %{id: 1, name: "a"}}
    iex> {:ok, [change]} = Ramal.Overlay.diff(overlay, base)
    iex> {change.kind, change.changes}
    {:updated, %{name: {"a", "b"}}}

# `merge_into`

```elixir
@spec merge_into(t(), t()) :: t()
```

Applies `other`'s entries on top of `overlay`, as if each had been put in turn.

Forking uses it: a fork starts as `merge_into(new(), parent)`. Because overlays are immutable maps, the fork and
the parent never see each other's later changes. The combination rules are the ones in the module doc, so merging
a `:deleted` entry over a `:created` one removes the row from the result.

## Example

    iex> parent = %{{MyApp.Herd, 1} => {:created, %{id: 1}}}
    iex> Ramal.Overlay.merge_into(Ramal.Overlay.new(), parent) == parent
    true

# `new`

```elixir
@spec new() :: t()
```

An empty overlay.

## Example

    iex> Ramal.Overlay.new()
    %{}

# `patch`

```elixir
@spec patch([record()], slice(), (record() -&gt; as_boolean(term())), keyword()) :: [
  record()
]
```

Applies a resource's overlay slice to the rows the base returned for a query.

`base_rows` are the rows the data layer returned for the query's filter, without limit and offset. The result is
what the same query would return on a base that already held the branch's changes:

1. a base row whose key is `:deleted` in the slice is dropped;
2. a base row whose key is `:updated` is replaced by the overlay record, or dropped when that record no longer
   satisfies `predicate`;
3. an overlay record the base did not return is added when it satisfies `predicate`. This covers created rows and
   updated rows that only match the filter after the update;
4. the rows are sorted, then `offset` and `limit` are applied. Doing that last is what lets a row created in the
   branch appear in a limited query.

`predicate` is called only on overlay records, since the base already applied the filter to its own rows. Callers
evaluate the query's filter in it, for example with `Ash.Filter.Runtime`.

## Options

  * `:key` (required) - a function returning the primary key of a row, the same keys the slice uses
  * `:sort` - a comparator `(a, b -> boolean)` as `Enum.sort/2` takes it; return `true` when `a` goes before or
    equal to `b`, which keeps the sort stable. Without it, base order is kept and added rows follow in key order
  * `:offset` - rows to skip after sorting, default `0`
  * `:limit` - the maximum number of rows, default no limit
  * `:replace` - a function `(base_row, overlay_record -> record)` that builds the row that takes a base row's place,
    for state the base row carries and the overlay record cannot, default the overlay record itself

## Example

    iex> base = [%{id: 1, n: 10}, %{id: 2, n: 20}]
    iex> slice = %{2 => :deleted, 3 => {:created, %{id: 3, n: 30}}}
    iex> Ramal.Overlay.patch(base, slice, fn _ -> true end, key: & &1.id, sort: &(&1.n >= &2.n), limit: 2)
    [%{id: 3, n: 30}, %{id: 1, n: 10}]

# `put_created`

```elixir
@spec put_created(t(), ref(), record()) :: t()
```

Records that the branch created the row `ref` with `record`.

See the table in the module doc for how it combines with an existing entry.

## Example

    iex> Ramal.Overlay.put_created(%{}, {MyApp.Herd, 1}, %{id: 1})
    %{{MyApp.Herd, 1} => {:created, %{id: 1}}}

# `put_deleted`

```elixir
@spec put_deleted(t(), ref()) :: t()
```

Records that the branch deleted the row `ref`.

Deleting a row the branch itself created removes the entry: the base never had that row, so there is nothing to hide.

## Example

    iex> overlay = Ramal.Overlay.put_deleted(%{}, {MyApp.Herd, 1})
    iex> overlay
    %{{MyApp.Herd, 1} => :deleted}
    iex> overlay |> Ramal.Overlay.put_created({MyApp.Herd, 2}, %{id: 2}) |> Ramal.Overlay.put_deleted({MyApp.Herd, 2})
    %{{MyApp.Herd, 1} => :deleted}

# `put_updated`

```elixir
@spec put_updated(t(), ref(), record()) :: t()
```

Records that the branch updated the row `ref`; `record` is the row as the update left it.

A row the branch created stays `:created`, with the new record.

## Example

    iex> overlay = Ramal.Overlay.put_created(%{}, {MyApp.Herd, 1}, %{id: 1, v: 1})
    iex> Ramal.Overlay.put_updated(overlay, {MyApp.Herd, 1}, %{id: 1, v: 2})
    %{{MyApp.Herd, 1} => {:created, %{id: 1, v: 2}}}

# `slice`

```elixir
@spec slice(t(), resource()) :: slice()
```

The entries for one resource, keyed by primary key.

## Example

    iex> overlay = %{{MyApp.Herd, 1} => :deleted, {MyApp.Paddock, 9} => {:created, %{id: 9}}}
    iex> Ramal.Overlay.slice(overlay, MyApp.Herd)
    %{1 => :deleted}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
