# `Coelho.Ecto.Type`
[🔗](https://github.com/nseaSeb/coelho/blob/main/lib/coelho/ecto/type.ex#L2)

Ecto type storing a Coelho document in a `:map` (`jsonb`) column.

The type carries the schema, so validation happens where Ecto already
reports failures — in the changeset:

    schema "posts" do
      field :body, Coelho.Ecto.Type, document_schema: MyApp.RichText.schema()
    end

The option is `:document_schema` and not `:schema` because Ecto injects
its own `:schema` key — the owning Ecto schema module — into every
parameterized type's options.

`Coelho.Ecto.rich_text/2` is the shorter way to write the same thing.

## What casting accepts

  * a document map, as `Jason.decode/1` or ProseMirror's `toJSON()`
    produce it — validated and normalised
  * a JSON string, which is what a form posts back from the editor's
    hidden input — decoded, then validated
  * `nil` and `""`, which cast to `nil`

A document failing validation makes the changeset invalid rather than
raising, and the individual schema violations are attached to the error
so a form can show them:

    {:error, changeset} = MyApp.Posts.create(%{body: hostile})
    changeset.errors
    #=> [body: {"is invalid rich text",
    #=>   [validation: :coelho,
    #=>    human: "block 1: unknown node type \"script\"",
    #=>    errors: ["content[0]: unknown node type \"script\""]]}]

## Writing past the cast

Validation lives in `cast/3`, which is what a changeset built from
parameters calls. `Ecto.Changeset.change/2` deliberately does not cast —
that is its purpose in Ecto, and it applies here like anywhere else — so
a map put through it is written as it stands:

    Ecto.Changeset.change(post, %{body: %{"type" => "nonsense"}})

is a valid changeset. Nothing checks the document, and the row then
renders as an error rather than as a page: `Coelho.Render` raises on a
node type the schema does not declare rather than emitting it, which is
the loud end of the two possible failures. Use `cast/3`, or hand
`change/2` something that has already been through
`Coelho.Document.validate/2` — what `Coelho.HTML.from_html/3` returns has
been.

## Loading

Values already in the database are loaded without re-validating them.
A schema that grew stricter after rows were written would otherwise make
old rows unreadable, which is a migration to run deliberately, not a
failure to discover at read time. Renderers are written accordingly:
`Coelho.Render` never derives markup structure from stored values.

That is a floor, not a guarantee about the row. A document written under
a looser schema, or by a direct SQL write, is not something `cast/3` ever
saw. Put it through `Coelho.Document.sanitize/2` before rendering it
somewhere a reader will see:

    post.body
    |> Coelho.sanitize(MyApp.RichText.schema())
    |> Coelho.to_html(MyApp.RichText.schema())

---

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