Validations
In Ash there are two areas where validations are used:
Attributes Those are used to validate the input data before it is saved to the database.
Actions Those are used to validate the input data before an action is executed.
Attributes Validations
Validations are used when constraints are not powerful enough.
Let me show you how to use validations with an online shop product
example.
We start with a clean slate. A fresh Ash app. Please move or delete an already existing Ash app if you have one under the directory name app
. Feel free to copy and paste the following lines in your terminal or do it step by step following relationship setup.
mix new --sup app && cd app
awk '/defp deps do/,/\[/ {
if ($0 ~ /\[/) {
print $0;
print "{:ash, \"~> 2.15.8\"}";
next;
}
} 1' mix.exs > mix.exs.tmp
mv mix.exs.tmp mix.exs
mix deps.get
echo '[
import_deps: [:ash],
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]' > .formatter.exs
mkdir config
echo 'import Config
config :app, :ash_apis, [App.Shop]' > config/config.exs
mix format
Please create the following files:
defmodule App.Shop.Product do
use Ash.Resource, data_layer: Ash.DataLayer.Ets
attributes do
uuid_primary_key :id
attribute :name, :string do
allow_nil? false
end
attribute :price, :decimal do
allow_nil? false
end
attribute :use_by_date, :date do
allow_nil? false
end
end
actions do
defaults [:create, :read, :update, :destroy]
end
code_interface do
define_for App.Shop
define :create
define :read
define :by_id, get_by: [:id], action: :read
define :by_name, get_by: [:name], action: :read
define :update
define :destroy
end
end
defmodule App.Shop do
use Ash.Api
resources do
resource App.Shop.Product
end
end
Custom Validations
We only add products to our shop that are not expired. For that we add a custom validation for the use_by_date
attribute. It must not be in the past. We need to write a custom validation for that.
defmodule App.Validations.InTheFutureOrToday do
use Ash.Resource.Validation
def validate(changeset, opts) do
case Ash.Changeset.fetch_argument_or_change(changeset, opts[:field]) do
:error ->
# in this case, they aren't changing the field
:ok
{:ok, value} ->
case Date.compare(Date.utc_today(), value) do
:gt ->
{:error, field: opts[:field], message: "must be in the future or today"}
_ ->
:ok
end
end
end
end
That validation can be used like this:
defmodule App.Shop.Product do
use Ash.Resource, data_layer: Ash.DataLayer.Ets
attributes do
uuid_primary_key :id
attribute :name, :string do
allow_nil? false
end
attribute :price, :decimal do
allow_nil? false
end
attribute :use_by_date, :date do
allow_nil? false
end
end
validations do
validate {App.Validations.InTheFutureOrToday, field: :use_by_date} (1)
end
actions do
defaults [:create, :read, :update, :destroy]
end
code_interface do
define_for App.Shop
define :create
define :read
define :by_id, get_by: [:id], action: :read
define :by_name, get_by: [:name], action: :read
define :update
define :destroy
end
end
1 | Here we are using the validation App.Validations.InTheFutureOrToday . |
Let’s try it out:
$ iex -S mix
iex(1)> App.Shop.Product.create!(%{
name: "Apple",
price: 0.1,
use_by_date: ~D[2008-11-10]
})
** (Ash.Error.Invalid) Input Invalid
* Invalid value provided for use_by_date: must be in the future or today.
nil
(ash 2.15.8) lib/ash/api/api.ex:2183: Ash.Api.unwrap_or_raise!/3
With custom validations you can solve pretty much any validation problem.
Action Validations
WIP. This is just a placeholder which will be filled with content in the next couple of days.
actions do
create :create do
validate compare(:age, greater_than_or_equal_to: 18)
end
end
# or
validations do
validate present([:foo, :bar], at_least: 1) do
on [:create, :update]
where present(:baz)
end
end