# `MishkaCmsMigration.Workers.Helpers.Writer`
[🔗](https://github.com/mishka-group/mishka_cms_migration/blob/v0.0.1/lib/mishka_cms_migration/workers/__helpers/writer.ex#L5)

What happens to one item once it is decided (`MishkaCmsMigration.Workers.Helpers.Decision`), and
its entry in the report (`call/3`, answering `MishkaCmsMigration.Report.entry/5`).

In an import an item to create or update has its content converted (`migration:content:convert`),
is offered to the plugins (`migration:item:before_import`), is handed to its target's `prepare/3`
(`MishkaCmsMigration.Target`), which may fetch what the write needs from the source or skip it,
is written through its target's create or update action (`MishkaCmsMigration.Target`'s
`writes_to/2` names the resource and the actions) as the admin who pressed Import (a create
that is a generic action, such as the core Media's `:upload`, is run as one, and an update that is
an upserting create, such as the core Setting's `:set`, is run as that create), and is recorded
(`MishkaCmsMigration.MigrationItem`'s `:record`) with the CMS record it was written to, in one
transaction (`Ash.transact/3`): either the CMS record and its item are both there, or neither is,
so a retry never meets a CMS record no item remembers. An action's `before_transaction` hook, such
as the Tailwind check a stylesheet's write runs, so runs inside that transaction by design, and Ash
is told so (`warn_on_transaction_hooks?: false`). An item skipped or failed is recorded with
its reason (`:record_outcome`), which keeps what the last write recorded; when even that cannot be
recorded, the item's entry says it failed, with why. A dry run writes nothing in the CMS: it
reports the plan and records it on each item (`:record_plan`, its outcome and reason as the
item's `plan` and `plan_reason`), which leaves alone what an import recorded. An item it would
create or update is first handed to its target's `waiting/2`, the dry run's stand-in for
`prepare/3`: one the import's `prepare/3` would leave waiting for a decision, or skip, is planned
so.

An item written otherwise than the source has it has its entry and its record say so: its
target's note (`MishkaCmsMigration.Target`'s `note/2`), then the pieces of its content no plugin
converted, kept as escaped text, joined by `"; "`, as in
`"draft_from_pending; unconverted: acme/callout, gallery"`. A dry run asks no plugin, so it names
every piece the source left to convert.

An item whose CMS record an admin archived, and decided to restore
(`MishkaCmsMigration.Workers.Helpers.Decision`'s `{:restored, …}`), is reported and recorded as
updated, its note led by `"restored"`. In an import the record is restored as the admin
(`MishkaCmsMigration.Workers.Helpers.Archived.restore/2`) in the item's own transaction: before
the write, for an item the source changed, or alone, recording the restored record's
`updated_at` for an item it did not, so the restore is not mistaken for an admin's edit, and
keeping the one it had for a record an admin changed before archiving it, or one the CMS already
held.

An item that is given a CMS record again after an admin decided on the one deleted for good
(`"deleted_in_cms"`, `MishkaCmsMigration.Workers.Helpers.Decision`) forgets that decision right
after it is recorded, in the item's transaction where it has one
(`MishkaCmsMigration.MigrationItem`'s `:forget_deleted_decision`), one more write for that item
alone: Import it again holds once, and a later deletion asks again.

An item connected to a record the CMS already held (`MishkaCmsMigration.Target`'s `existing/2`),
or mapped to one by an admin, is not written: in an import it is recorded against that record,
with no `target_updated_at`, as skipped, `"connected"`; a dry run reports it so. An item the
target's `prepare/3` finds to be a record the CMS holds (`{:connected, record, reason}`) is
recorded the same way, as skipped with `reason`. What the target's `recorded/2` answers is
recorded on the item of a record written. An item whose
conflict waits for an admin's decision is not written either: it is recorded as `:undecided`,
with the conflict as its reason, and the next run asks again.

The target is handed the item's decisions (`MishkaCmsMigration.Target`'s context, `decisions`),
so what an admin decided on a conflict holds for its note and its write, and what its
`existing/2` found for the item's write in its read of the page (`prepared`), so its note and its
write need not read it again.

An item with a parent (`MishkaCmsMigration.Source.Item`'s `parent`), which the walk settles
first, is written under the CMS record its parent's item recorded: the target is handed that
record's id as its context's `parent_id`. When the parent has none — it waits for a decision,
failed, was never written, or the source never listed it — the item is skipped with a reason that
names the parent and what unblocks it (`MishkaCmsMigration.Workers.Helpers.Needs.parent/2`), read
from the parent's item this lookup already found, so it is not written out of its place, and the
run that writes the parent writes it too. An item an admin decided to import anyway
(`MishkaCmsMigration.MigrationItem.without_parent?/1`) is written then with no parent, at the
top level, and its note says so first (`"without_parent: <parent>"`).

A plugin, a target or the CMS action that raises or exits while an item is converted, offered or
written fails that item alone, with the exception's message as its reason. One the CMS's policies
refuse the admin fails as `"forbidden: <kind>"`, "You may not change <kinds> on this site".

# `call`

```elixir
@spec call(map(), MishkaCmsMigration.Source.Item.t(), term()) ::
  MishkaCmsMigration.Report.entry()
```

Settles `item` by `plan` and answers its entry in the report:

  * `{:created, target, nil, decisions}` or `{:updated, target, record, decisions}` — written
    in an import, reported and recorded as the item's plan in a dry run, with its target's note
    and the pieces of its content left unconverted as its reason, or as what its target's
    `waiting/2` answers instead; `{:created, target, nil, decisions, prepared}` is settled the
    same way, with `prepared`, what the target's `existing/2` found for the write, as its
    context's `prepared`;
  * `{:restored, target, record, decisions, then}` — `record` restored and the item settled as
    `then` says, reported and recorded as updated, with `"restored"` leading its note;
  * `{:connected, target, record}` or `{:mapped, target, record}` — recorded against `record`
    in an import, never written, and reported as skipped, `"connected"`;
  * `{:skipped, reason}`, `{:failed, reason}` or `{:undecided, conflict}` — recorded with its
    reason, as what an import did or as a dry run's plan; an item whose row could not be read
    has only its kind and, when the row named one, its source id, and is recorded only with one;
  * `:duplicate` — the source listed its id before: reported, never recorded.

---

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