Ash Integration
View SourceElex ships an optional Ash resource validation that type-checks expression strings stored on Ash attributes. The validation runs when the attribute changes on a changeset.
Setup
Add both Elex and Ash to your dependencies:
def deps do
[
{:elex, "~> 0.3.0"},
{:ash, "~> 3.22"}
]
endElex.AshValidation is compiled only when Ash is available. Without Ash in
your dependency tree, the module is not present.
Basic usage
Validate that a :formula attribute contains an expression returning a decimal:
defmodule MyApp.Product do
use Ash.Resource
attributes do
attribute :formula, :string do
allow_nil? false
end
end
validations do
validate Elex.AshValidation,
attribute: :formula,
context: Elex.new_context(),
expected_type: :decimal
end
endWhen a user submits an invalid expression, Ash returns a changeset error with a human-readable message from Elex.
Options
| Option | Required | Description |
|---|---|---|
:attribute | yes | Atom name of the string attribute holding the expression |
:context | yes | Elex.Context with allowed variables and functions |
:expected_type | yes | :decimal, :boolean, :string, or a catalog category atom (:length) when the context has a units catalog. Category atoms are passed as category: to Elex.validate/3 — not compared to a returned :length atom. Validate returns %Elex.Dimension{} for unitful results (length, length | time). |
:add_value_type_from_attribute | no | Attribute atom; adds a "value" variable typed from that attribute's current value |
:description | no | Custom text included in validation error messages |
Context with variables
Pass a context that includes every variable your users may reference:
context =
Elex.new_context()
|> Elex.add_variables!(%{
"price" => 0,
"quantity" => 0
})
validations do
validate Elex.AshValidation,
attribute: :formula,
context: context,
expected_type: :decimal
endVariables in the context need types for validation to succeed, but their values are not used during validation — only types matter.
Validating against the current value
Use :add_value_type_from_attribute when expressions may reference a value
variable representing the attribute being validated:
attributes do
attribute :threshold, :decimal
attribute :condition, :string
end
validations do
validate Elex.AshValidation,
attribute: :condition,
context: Elex.new_context(),
expected_type: :boolean,
add_value_type_from_attribute: :threshold
endThis lets users write expressions like value > 100 where value is typed as
:decimal from the :threshold attribute.
Units: expected_type as a category
When the context has a units catalog, :expected_type may be a category atom.
That becomes Elex.validate(..., category: :length) — not a comparison of
validate's return value to :length. Dimensionless :decimal, :boolean,
and :string are unchanged.
validations do
validate Elex.AshValidation,
attribute: :distance_formula,
context: length_context,
expected_type: :length
end"1mm + 2m" succeeds. "1 + 2" fails with length was expected, got number. A
speed-compatible quotient such as "1cm / 1s" succeeds only when
expected_type is :speed (or another category whose formula is
length | time).
What gets validated
On change, Elex.AshValidation:
- Reads the new value of the target attribute (skips validation when unchanged)
- Calls
Elex.validate/3. Catalog category:expected_typevalues are passed ascategory:; primitive types are checked against the returned atom - Returns
:okor anAsh.Error.Changes.InvalidAttributeerror
Parse and type errors surface as attribute errors with the expression string as the invalid value.
Further reading
Elex.AshValidation— full API reference- Getting Started — building contexts and validating expressions
- Expression Language — syntax reference