# `Masu.Write`
[🔗](https://github.com/hattoriai/masu/blob/v0.7.0/lib/masu/write.ex#L1)

A write of a grid's rows: cells edited, new rows, rows deleted or
brought back. Every change a person makes to the rows goes through one,
whoever saves it.

The grid builds the write, and hands it to your function for its
operation (`on_update`, `on_create`, `on_delete`, `on_restore` on
`Masu.Components.data_grid/1`), which gives it back with a result for
each change. Without one, the grid saves it its own way
(`Masu.Write.save/1`: the repo of an Ecto source, or its copy of a
list), with `changeset`.

    <.data_grid id="orders" source={Order} repo={Repo} scope={@current_scope}
      on_update={&OrdersGrid.update/1}>

    defmodule MyAppWeb.OrdersGrid do
      alias MyApp.Orders

      # A context function, row by row.
      def update(write) do
        Masu.Write.each(write, &Orders.update_order(write.scope, &1.row, &1.params))
      end
    end

By the time your function gets it, the grid has done its part: each
row is found again through the grid's own query (a person writes only
the rows they may see), the cells that do not edit, or that the row's
state makes read-only (`row_editable`, a column's `editable`), are
refused, and so are the cells someone changed meanwhile (see
"Two people, one cell" in the Editing guide). A change has only what
is left.

## What a write has

  * `op` - `:update`, `:create`, `:delete` or `:restore`;
  * `changes` - the `Masu.Write.Change`s, one for each row;
  * `origin` - what the person did: `:edit` (typed in a cell),
    `:paste`, `:fill`, `:clear`, `:undo`, `:redo` (cells); `:draft` (a
    new row), `:paste` (rows of a paste); `:rows` (the rows of a
    range), `:selection` (the rows selected), `:redo` (a delete again);
    `:undo` (rows back); `:test` from `Masu.Test`;
  * `scope` - the grid's `scope`: the current user, their
    organization, as your app has it (`@current_scope`);
  * `by` - who makes it: on a grid in an Issho room (`issho`), and for
    an agent's tools, an `Issho.Identity` (a person, an agent and whom
    it acts for), the same as every component's callbacks get; else the
    grid's `user`;
  * `grid` - the id of the grid;
  * `ref` - the write's reference, for `async/1` and `Masu.reply/2`.

## Results

Each change gets one, from the function that writes it:

  * `each/2` - a function of each change, `{:ok, row}` or
    `{:error, reason}` (`:ok` for a delete);
  * `all/2` - one function of every change, for a transaction of the
    whole batch (`Ecto.Multi`): `{:ok, rows}`, in the order of the
    changes, or `{:error, reason}`, which refuses them all;
  * `ok/3` and `error/3` - a result for one change;
  * `save/1` - the grid's own way, for the changes without a result;
  * `async/1` - the changes without a result are saved later, by a job
    of yours: the grid shows them saving until `Masu.reply/2`.

A reason is an `Ecto.Changeset` (its errors go on the cells of its
fields), a message (on every cell of the row), or `[{key, message}]`
of column keys. A change left without a result is a mistake, and the
grid raises.

The results compose. Your function may save the grid's way and do
more, or save each row its own way and the rest the grid's:

    def update(write) do
      write
      |> Masu.Write.save()
      |> tap(&Audit.log(&1.scope, Masu.Write.saved(&1)))
    end

    def update(%Masu.Write{origin: :paste} = write) do
      Masu.Write.all(write, &Orders.update_many(write.scope, &1))
    end

    def update(write), do: Masu.Write.save(write)

## Later: a job

A write that takes long (a paste of 5,000 cells into a system that
takes its time, an import of new rows through a queue) can be saved by
a job. `async/1` says that the changes without a result will be
saved; `ref` reaches the grid from any process (an Oban job, on any
node of the cluster):

    def update(write) do
      %{ref: write.ref, changes: Enum.map(write.changes, &Map.take(&1, [:id, :params]))}
      |> MyApp.Workers.SaveCells.new()
      |> Oban.insert!()

      Masu.Write.async(write)
    end

    # In the job:
    Masu.reply(ref, :ok)
    Masu.reply(ref, {:error, "The ledger is closed."})

The cells show saving until the reply; then the grid reads its rows
again, and an error refuses the changes, which show their old values.
New rows and rows brought back need their ids at once: they cannot be
saved later.

# `op`

```elixir
@type op() :: :update | :create | :delete | :restore
```

# `reason`

```elixir
@type reason() ::
  Ecto.Changeset.t() | String.t() | [{String.t() | atom(), String.t()}]
```

# `result`

```elixir
@type result() :: {:ok, term()} | {:error, reason()} | :pending | saved_stale()
```

# `t`

```elixir
@type t() :: %Masu.Write{
  by: term(),
  changes: [Masu.Write.Change.t()],
  grid: String.t(),
  grid_config: map(),
  op: op(),
  origin: atom(),
  recorded: [non_neg_integer()],
  ref: String.t(),
  results: %{required(non_neg_integer()) =&gt; result()},
  scope: term()
}
```

# `all`

```elixir
@spec all(t(), ([Masu.Write.Change.t()] -&gt; term())) :: t()
```

Gives the changes without a result one result, of `fun` of all of
them: `{:ok, rows}`, the rows as saved in the order of the changes (or
`:ok` for a delete), or `{:error, reason}`, which refuses every one
(the whole batch, as a transaction does). `{:error, change, reason}`
says which change it was: the others say that they were not saved.

# `async`

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

The changes without a result are saved later, by a process of yours,
which says how it went with `Masu.reply(write.ref, result)`: `:ok`,
`{:ok, message}` or `{:error, message}`. Not for `:create` and
`:restore`, whose rows need their ids at once.

# `each`

```elixir
@spec each(t(), (Masu.Write.Change.t() -&gt; term())) :: t()
```

Gives each change without a result the result of `fun`: `{:ok, row}`,
with the row as saved, or `{:error, reason}`. For `:delete`, `:ok` or
`{:ok, _}` is the row deleted.

# `error`

```elixir
@spec error(t(), Masu.Write.Change.t(), reason()) :: t()
```

The result of one change: refused, for `reason`.

# `failed`

```elixir
@spec failed(t()) :: [{Masu.Write.Change.t(), reason()}]
```

The changes refused, with their reasons.

# `ok`

```elixir
@spec ok(t(), Masu.Write.Change.t(), term()) :: t()
```

The result of one change: saved, as `row`.

# `open`

```elixir
@spec open(t()) :: [Masu.Write.Change.t()]
```

The changes without a result yet.

# `record`

```elixir
@spec record(t(), Masu.Write.Change.t(), term()) :: t()
```

Records a change that your function saved, as `row`, in the grid's
history (`Masu.History`) now: in the transaction your function runs
it in, when the history is in the same repo. The grid does not record
it again after your function. Without a history, it does nothing.

    def update(write) do
      Masu.Write.each(write, fn change ->
        Repo.transaction(fn ->
          order = Orders.update!(change.row, change.params)
          Masu.Write.record(write, change, order)
          order
        end)
      end)
    end

Gives the write, which knows the change is recorded.

# `result`

```elixir
@spec result(t(), Masu.Write.Change.t()) :: result() | nil
```

The result of a change, or nil.

# `save`

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

Saves the changes without a result the grid's own way:

  * `:update` - `row |> changeset.(params)`, then `Repo.update` for an
    Ecto source, each row read, compared again and saved in a
    transaction that locks it; for a list, the changeset applied;
  * `:create` - `changeset.(struct, params)`, inserted by the repo, or
    applied for a list; the grid's `create_with` fields go on the
    struct first, and on the changeset after, over what it cast;
  * `:delete` - `Repo.delete`, in a transaction that locks the row; a
    list's row taken out of its copy;
  * `:restore` - the row inserted again; a list's put back.

An update and a new row need the grid's `changeset` (and `struct`).

# `saved`

```elixir
@spec saved(t()) :: [term()]
```

The rows saved: as saved, in the order of their changes.

---

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