View Source Predicates

Dx allows you to add predicates to your schema. Predicates are like virtual fields, but instead of storing values, you define what the value should be, based on conditions.

Example: boolean predicate

Say we have a ToDo list app with a Todo.List schema type.

defmodule Todo.List do
  use Ecto.Schema
  use Dx.Ecto.Schema, repo: Todo.Repo

  schema "lists" do
    field :archived_at, :utc_datetime
  end

  infer archived?: false, when: %{archived_at: nil}
  infer archived?: true
end

Here, we define a predicate archived? on our Todo.List schema. It has the value false when the field archived_at is nil. If this condition doesn't match, Dx will look at the next rule and assign the value true. Since there is no condition for this last rule, it will always match.

Tip: It is a good practice to always have a last rule without condition to define a fallback value.

Usage

This predicate can now be used like a field, as long as you use an Dx to to evaluate it, such as Dx.get!/2:

# loading a predicate
iex> %Todo.List{archived_at: nil}
...> |> Dx.get!(:archived?)
false

iex> %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]}
...> |> Dx.get!(:archived?)
true

Example: multi-value predicate

Instead of assigning true or false, we might define a predicate state that can be easily extended later on. We can even use the existing predicate and reference it in our new rule:

defmodule Todo.List do
  use Ecto.Schema
  use Dx.Ecto.Schema, repo: Todo.Repo

  schema "lists" do
    field :archived_at, :utc_datetime
  end

  infer archived?: false, when: %{archived_at: nil}
  infer archived?: true

  infer state: :archived, when: %{archived?: true}
  infer state: :active
end

Usage

Just as with archived?, we can now use state as if it was a field when using Dx:

iex> %Todo.List{archived_at: nil}
...> |> Dx.get!(:state)
:active

iex> %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]}
...> |> Dx.get!(:state)
:archived