How to do a custom validation of an attribute?

How can I do a custom validation in the following resource to make sure that the attribute use_by_date is today or later but not in the past?

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
      constraints min_length: 3
    end

    attribute :price, :decimal

    attribute :use_by_date, :date do
      allow_nil? false
    end
  end

  actions do
    defaults [:create, :read]
  end
end
1 Like

What you’re looking for is validations. I don’t think our built in validations will do datetime comparisons, but you can do it with a custom validation.

validate {MyApp.Validations.InTheFuture, field: :use_by_date} 
defmodule MyApp.Validations.InTheFuture 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, or providing an argument by the same name
         :ok

      {:ok, value} ->
        if DateTime.after?(DateTime.utc_now(), value) do
          :ok
        else
           {:error, field: opts[:field], message: "must be in the future"}
        end
    end
  end
end

EDIT: The validations guide is here: Validations — ash v2.15.6

3 Likes

For the archive and Google: The code in the resource would look like this.

  attributes do
    # [...]

    attribute :use_by_date, :date do
      allow_nil? false
    end
  end

  validations do
    validate {App.Validations.InTheFuture, field: :use_by_date}
  end
1 Like