A Coelho document as an Ash attribute, stored in a :map column.
Coelho.Ecto.Type targets an Ecto schema, and Ash does not go through
Ecto.Type for its own attributes, so an Ash resource declaring
attribute :body, :map gets a map and no validation at all.
Why this is a use rather than a ready-made type
Coelho does not depend on Ash — not even optionally. An optional
dependency would be the usual way to ship a module that needs another
library at compile time, but Ash depends on :stream_data in every
environment, and Coelho keeps :stream_data to :dev and :test for its
property tests. Reconciling the two means loosening Coelho's own
dependencies for every application that will never use Ash.
So the type is a macro that expands in your application, where Ash is present by definition. It costs one module:
defmodule MyApp.RichText.Type do
use Coelho.Ash.Type
endand then reads the way any other Ash type does:
attribute :cgv_doc, MyApp.RichText.Type do
constraints document_schema: MyApp.RichText.cgv_schema()
endThe schema arrives as a constraint rather than as an option on the
attribute, because that is where Ash puts per-attribute configuration and
where Ash.Resource.Info will show it.
Constraints
:document_schema— required, theCoelho.Schemato validate against:sanitize?— when true, a value that fails validation is put throughCoelho.Document.sanitize/2and accepted instead of rejected. Defaults tofalse. Turn it on for an import path where refusing the whole document is worse than keeping a poorer one; leave it off wherever a person is typing, so they are told rather than silently corrected
What casting accepts
- a document map, as ProseMirror's
toJSON()produces it - a JSON string, which is what a form posts back from the editor's hidden input
niland"", which cast tonil
What an invalid document looks like
An Ash.Error.Changes.InvalidAttribute on the attribute, whose vars
carry the location in the document tree — which is what lets a LiveView
form say more than "is invalid":
%Ash.Error.Changes.InvalidAttribute{
field: :cgv_doc,
message: "is not valid rich text (%{location}: %{reason})",
vars: [location: "content[0].attrs.href", reason: "scheme \"javascript\" is not allowed", ...]
}vars[:errors] holds every failure, formatted, not only the first.
Loading
Values already in the database are loaded without being re-validated, for
the reason Coelho.Ecto.Type gives: a schema that grew stricter after
rows were written would otherwise make old rows unreadable, which is a
migration to run deliberately. Put a stored document through
Coelho.Document.sanitize/2 before rendering it.
Summary
Functions
Casts user supplied input — a document map, or the JSON a form posts back.
Reads a value back out of the column, without re-validating it.
The constraint schema the generated type declares.
Writes a value to the column.
Functions
Casts user supplied input — a document map, or the JSON a form posts back.
Reads a value back out of the column, without re-validating it.
@spec constraints() :: keyword()
The constraint schema the generated type declares.
Writes a value to the column.