From 93fb9bc16cde734992f0952a37d4598922d9086a Mon Sep 17 00:00:00 2001 From: arnodirlam Date: Wed, 16 Oct 2024 10:47:18 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20elixir-d?= =?UTF-8?q?x/dx@8ae6621d121d6cbd1694ffa781edcb1e7dd3ccc2=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 00_intro.html | 2 +- 01_predicates.html | 38 +- 02_associations.html | 64 ++-- 03_conditions.html | 38 +- 04_references.html | 26 +- 05_arguments.html | 16 +- 06_thinking_in_dx.html | 70 ++-- 99_outro.html | 2 +- Dx.Defd.html | 16 +- Dx.Ecto.Query.Batches.html | 112 +++--- Dx.Ecto.Query.TranslationError.html | 4 +- Dx.Ecto.Schema.html | 2 +- Dx.Error.Generic.html | 4 +- Dx.Error.NotLoaded.html | 4 +- Dx.Error.RulesNotFound.html | 4 +- Dx.Error.Timeout.html | 4 +- Dx.Result.html | 524 ++++++++++++++-------------- Dx.Scope.html | 22 +- Dx.html | 24 +- dx.epub | Bin 65176 -> 65188 bytes full_reference.html | 76 ++-- welcome.html | 2 +- 22 files changed, 527 insertions(+), 527 deletions(-) diff --git a/00_intro.html b/00_intro.html index e4ce4f85..ea016ab0 100644 --- a/00_intro.html +++ b/00_intro.html @@ -112,7 +112,7 @@

- + View Source diff --git a/01_predicates.html b/01_predicates.html index b2f41779..b1dcee6e 100644 --- a/01_predicates.html +++ b/01_predicates.html @@ -112,7 +112,7 @@

- + View Source @@ -129,29 +129,29 @@

Example: boolean predicate

-

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

defmodule Todo.List do
+

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
+  schema "lists" do
     field :archived_at, :utc_datetime
-  end
+  end
 
-  infer archived?: false, when: %{archived_at: nil}
+  infer archived?: false, when: %{archived_at: nil}
   infer archived?: true
-end

Here, we define a predicate archived? on our Todo.List schema. +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?)
+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?)
+iex> %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]}
+...> |> Dx.get!(:archived?)
 true

@@ -160,25 +160,25 @@

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
+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
+  schema "lists" do
     field :archived_at, :utc_datetime
-  end
+  end
 
-  infer archived?: false, when: %{archived_at: nil}
+  infer archived?: false, when: %{archived_at: nil}
   infer archived?: true
 
-  infer state: :archived, when: %{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)
+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)
+iex> %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]}
+...> |> Dx.get!(:state)
 :archived

diff --git a/02_associations.html b/02_associations.html index 4ce89b08..5d367f3d 100644 --- a/02_associations.html +++ b/02_associations.html @@ -112,7 +112,7 @@

- + View Source @@ -122,57 +122,57 @@

Dx allows to easily traverse associations to access fields or even -predicates defined on associated records.

Say our Todo.List schema from the previous guide now has_many tasks:

defmodule Todo.List do
+predicates defined on associated records.

Say our Todo.List schema from the previous guide now has_many tasks:

defmodule Todo.List do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "lists" do
+  schema "lists" do
     field :archived_at, :utc_datetime
 
     has_many :tasks, Todo.Task
-  end
+  end
 
-  infer archived?: false, when: %{archived_at: nil}
+  infer archived?: false, when: %{archived_at: nil}
   infer archived?: true
 
-  infer state: :archived, when: %{archived?: true}
+  infer state: :archived, when: %{archived?: true}
   infer state: :active
-end

In return, we add a Task schema that belongs_to a List:

defmodule Todo.Task do
+end

In return, we add a Task schema that belongs_to a List:

defmodule Todo.Task do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "tasks" do
+  schema "tasks" do
     field :completed_at, :utc_datetime
 
     belongs_to :list, Todo.List
-  end
+  end
 
-  infer completed?: false, when: %{completed_at: nil}
+  infer completed?: false, when: %{completed_at: nil}
   infer completed?: true
-end

+end

belongs_to

Say we want a Task to be archived? when the List it belongs to is archived. -We could write a similar rule on the Todo.Task schema as we have on the List:

  infer archived?: false, when: %{list: %{archived_at: nil}}
+We could write a similar rule on the Todo.Task schema as we have on the List:

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

The archived? predicate looks at the associated list (defined using belongs_to) and its field archived_at, and compares that to nil. If it's nil then the Task's predicate archived? is false, otherwise it's true.

However, since we've already defined this logic on the List, we can also use the predicate -on the associated List instead, and change things around a bit:

  infer archived?: true, when: %{list: %{archived?: true}}
+on the associated List instead, and change things around a bit:

  infer archived?: true, when: %{list: %{archived?: true}}
   infer archived?: false

Usage

Like before, we can use Dx.get!/2 to evaluate the predicate, -but only if the association is (pre)loaded:

iex> list = %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]} |> Todo.Repo.insert!()
-...> %Todo.Task{completed_at: nil, list: list}
-...> |> Dx.get!(:archived?)
-true

If the association is not (pre)loaded, Dx.get!/2 will raise an error:

iex> list = %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]} |> Todo.Repo.insert!()
-...> %Todo.Task{completed_at: nil, list: list}
-...> |> Todo.Repo.insert!() |> Todo.Repo.reload!()  # insert and reload without associations
-...> |> Dx.get!(:archived?)
-** (Dx.Error.NotLoaded) Association list is not loaded on nil. Cannot get path: nil

To allow Dx to load associations as needed, use Dx.load!/2 instead:

iex> list = %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]} |> Todo.Repo.insert!()
-...> %Todo.Task{completed_at: nil, list: list}
-...> |> Todo.Repo.insert!() |> Todo.Repo.reload!()  # insert and reload without associations
-...> |> Dx.load!(:archived?)
+but only if the association is (pre)loaded:

iex> list = %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]} |> Todo.Repo.insert!()
+...> %Todo.Task{completed_at: nil, list: list}
+...> |> Dx.get!(:archived?)
+true

If the association is not (pre)loaded, Dx.get!/2 will raise an error:

iex> list = %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]} |> Todo.Repo.insert!()
+...> %Todo.Task{completed_at: nil, list: list}
+...> |> Todo.Repo.insert!() |> Todo.Repo.reload!()  # insert and reload without associations
+...> |> Dx.get!(:archived?)
+** (Dx.Error.NotLoaded) Association list is not loaded on nil. Cannot get path: nil

To allow Dx to load associations as needed, use Dx.load!/2 instead:

iex> list = %Todo.List{archived_at: ~U[2022-02-02 22:22:22Z]} |> Todo.Repo.insert!()
+...> %Todo.Task{completed_at: nil, list: list}
+...> |> Todo.Repo.insert!() |> Todo.Repo.reload!()  # insert and reload without associations
+...> |> Dx.load!(:archived?)
 # loads the associated list
 true

@@ -181,20 +181,20 @@

has_many

We can also define predicates based on a has_many association. -Dx generally treats conditions on a list of records like an Enum.any? condition:

defmodule Todo.List do
+Dx generally treats conditions on a list of records like an Enum.any? condition:

defmodule Todo.List do
   # ...
 
-  infer in_progress?: true, when: %{tasks: %{completed?: true}}
+  infer in_progress?: true, when: %{tasks: %{completed?: true}}
   infer in_progress?: false
-end

The predicate in_progress? is true if there's any Task associated that has completed?: true. -Otherwise, if there's no Task associated that has completed?: true, in_progress? is false.

Putting it all together, we can extend our state predicate on the Todo.List schema:

defmodule Todo.List do
+end

The predicate in_progress? is true if there's any Task associated that has completed?: true. +Otherwise, if there's no Task associated that has completed?: true, in_progress? is false.

Putting it all together, we can extend our state predicate on the Todo.List schema:

defmodule Todo.List do
   # ...
 
-  infer state: :archived, when: %{archived?: true}
-  infer state: :in_progress, when: %{tasks: %{completed?: true}}
-  infer state: :ready, when: %{tasks: %{}}
+  infer state: :archived, when: %{archived?: true}
+  infer state: :in_progress, when: %{tasks: %{completed?: true}}
+  infer state: :ready, when: %{tasks: %{}}
   infer state: :empty
-end

What does the :ready rule do? +end

What does the :ready rule do? It checks whether there's any Task, without any condition on the Task. So if the List is not archived, and there are no completed tasks, but there is a Task, :state is :ready. Otherwise :state is :empty.

This might be hard to grasp, but it will hopefully become clearer in the next guide...

diff --git a/03_conditions.html b/03_conditions.html index 35c23038..98c3bb4e 100644 --- a/03_conditions.html +++ b/03_conditions.html @@ -112,7 +112,7 @@

- + View Source @@ -131,27 +131,27 @@

about how to define a condition, use a Map.

When using a Map with multiple elements in a condition, all of its elements must match for the whole condition to match. This is similar to pattern-matching in Elixir code.

Say we want a Todo.List to only be archivable? if it's not -archived yet and all of its tasks are completed.

defmodule Todo.List do
+archived yet and all of its tasks are completed.

defmodule Todo.List do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "lists" do
+  schema "lists" do
     field :archived_at, :utc_datetime
 
     belongs_to :created_by, Todo.User
-  end
+  end
 
-  infer archived?: false, when: %{archived_at: nil}
+  infer archived?: false, when: %{archived_at: nil}
   infer archived?: true
 
   infer archivable?: true,
-        when: %{
+        when: %{
           archived?: false,
-          tasks: {:all?, %{completed?: true}}
-        }
+          tasks: {:all?, %{completed?: true}}
+        }
 
   infer archivable?: false
-end

+end

@@ -161,18 +161,18 @@

major difference to pattern-matching. It might not feel intuitive at first, but gets familiar fast and becomes very useful once you got used to it.

Say we want a Todo.List to be archivable? if its :state we -defined in the previous chapter is either :completed or :ready.

defmodule Todo.List do
+defined in the previous chapter is either :completed or :ready.

defmodule Todo.List do
   # ...
 
-  infer state: :archived, when: %{archived?: true}
-  infer state: :completed, when: %{tasks: {:all?, %{completed?: true}}}
-  infer state: :in_progress, when: %{tasks: %{completed?: true}}
-  infer state: :ready, when: %{tasks: %{}}
+  infer state: :archived, when: %{archived?: true}
+  infer state: :completed, when: %{tasks: {:all?, %{completed?: true}}}
+  infer state: :in_progress, when: %{tasks: %{completed?: true}}
+  infer state: :ready, when: %{tasks: %{}}
   infer state: :empty
 
-  infer archivable?: true, when: %{state: [:completed, :ready]}
+  infer archivable?: true, when: %{state: [:completed, :ready]}
   infer archivable?: false
-end

This is also a good example for defining predicates based on other +end

This is also a good example for defining predicates based on other predicates if they reflect how you actually think about them. Dx will find an efficient way to evaluate them.

@@ -181,16 +181,16 @@

Not

Negations can be expressed by wrapping a condition in a :not tuple.

Another way of expressing the archived? predicate from the first -guide would thus be:

defmodule Todo.List do
+guide would thus be:

defmodule Todo.List do
   # ...
 
-  infer archived?: true, when: %{archived_at: {:not, nil}}
+  infer archived?: true, when: %{archived_at: {:not, nil}}
   infer archived?: false
 
   # previously:
   # infer archived?: false, when: %{archived_at: nil}
   # infer archived?: true
-end
+
end

diff --git a/04_references.html b/04_references.html index 0bfaf35e..5b5f8b09 100644 --- a/04_references.html +++ b/04_references.html @@ -112,7 +112,7 @@

- + View Source @@ -122,42 +122,42 @@

Often times, we need to compare values in different fields with -each other, not with fixed values. This is where references come in.

Say we want to add a by_owner? predicate to a Todo.Task:

defmodule Todo.Task do
+each other, not with fixed values. This is where references come in.

Say we want to add a by_owner? predicate to a Todo.Task:

defmodule Todo.Task do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "tasks" do
+  schema "tasks" do
     field :completed_at, :utc_datetime
 
     belongs_to :list, Todo.List
     belongs_to :created_by, Todo.User
-  end
+  end
 
-  infer by_owner?: true, when: %{created_by_id: {:ref, [:list, :created_by_id]}}
+  infer by_owner?: true, when: %{created_by_id: {:ref, [:list, :created_by_id]}}
   infer by_owner?: false
-end

+end

Operators

By default, all comparisons need to match exactly. However, other comparisons are possible as well.

Say we support completing tasks on an already archived list. -And we want to add a predicate completed_later? to capture that.

defmodule Todo.Task do
+And we want to add a predicate completed_later? to capture that.

defmodule Todo.Task do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "tasks" do
+  schema "tasks" do
     field :completed_at, :utc_datetime
 
     belongs_to :list, Todo.List
     belongs_to :created_by, Todo.User
-  end
+  end
 
-  infer completed_later?: false, when: %{completed?: false}
-  infer completed_later?: false, when: %{list: %{archived?: false}}
-  infer completed_later?: true, when: %{completed_at: {:gt, {:ref, [:list, :archived_at]}}}
+  infer completed_later?: false, when: %{completed?: false}
+  infer completed_later?: false, when: %{list: %{archived?: false}}
+  infer completed_later?: true, when: %{completed_at: {:gt, {:ref, [:list, :archived_at]}}}
   infer completed_later?: false
-end

The Todo.Task must already by completed? and the Todo.List archived?. +end

The Todo.Task must already by completed? and the Todo.List archived?. In particular, the Todo.Task must be completed_at after the Todo.List was archived.

Operators can also compare to fixed values (not references).

Supported operators

Operators with all aliases:

  • Greater than: :gt, :>, :greater_than, :after
  • Greater than or equal: :gte, :>=, :greater_than_or_equal, :on_or_after, :at_or_after
  • Less than: :lt, :<, :less_than, :before
  • Less than or equal: :lte, :<=, :less_than_or_equal, :on_or_before, :at_or_before
diff --git a/05_arguments.html b/05_arguments.html index 701da484..c4f64552 100644 --- a/05_arguments.html +++ b/05_arguments.html @@ -112,7 +112,7 @@

- + View Source @@ -124,21 +124,21 @@

To pass in data from the caller context and make it available in the rules, there's the args option. Any args passed to the Dx API function will be available in rules as if args was an association on the current root record.

Say we implement some authorization, where a user can archive a Todo.List only if they are an admin, or they are the owner of the list. -We pass in the currently logged-in user struct, which was already loaded as part of authentication.

Dx.load!(list, args: [current_user: current_user])

The current_user is then available within args, including any -fields, associations and predicates defined on it.

defmodule Todo.List do
+We pass in the currently logged-in user struct, which was already loaded as part of authentication.

Dx.load!(list, args: [current_user: current_user])

The current_user is then available within args, including any +fields, associations and predicates defined on it.

defmodule Todo.List do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "lists" do
+  schema "lists" do
     field :archived_at, :utc_datetime
 
     belongs_to :created_by, Todo.User
-  end
+  end
 
-  infer can_archive?: true, when: %{args: %{current_user: %{is_admin?: true}}}
-  infer can_archive?: true, when: %{created_by_id: {:ref, [:args, :current_user, :id]}}
+  infer can_archive?: true, when: %{args: %{current_user: %{is_admin?: true}}}
+  infer can_archive?: true, when: %{created_by_id: {:ref, [:args, :current_user, :id]}}
   infer can_archive?: false
-end
+
end

diff --git a/06_thinking_in_dx.html b/06_thinking_in_dx.html index fdc2f4c2..eadc7f95 100644 --- a/06_thinking_in_dx.html +++ b/06_thinking_in_dx.html @@ -112,7 +112,7 @@

- + View Source @@ -130,47 +130,47 @@

Example

Say we have a complex requirement to implement:

A user can archive a Todo list, but only if they created it, or if they have an "admin" role, -and only if all tasks in the list are completed.

Let's assume we only have the schema, and no other functions, helpers, or rules defined.

defmodule Todo.User do
+and only if all tasks in the list are completed.

Let's assume we only have the schema, and no other functions, helpers, or rules defined.

defmodule Todo.User do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "users" do
+  schema "users" do
     has_many :roles, Todo.UserRole
-  end
-end
+  end
+end
 
-defmodule Todo.UserRole do
+defmodule Todo.UserRole do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "user_roles" do
-    field :name, Ecto.Enum, values: [:moderator, :admin, :super_admin]
+  schema "user_roles" do
+    field :name, Ecto.Enum, values: [:moderator, :admin, :super_admin]
 
     belongs_to :user, Todo.User
-  end
-end
+  end
+end
 
-defmodule Todo.List do
+defmodule Todo.List do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "lists" do
+  schema "lists" do
     field :archived_at, :utc_datetime
 
     belongs_to :created_by, Todo.User
-  end
-end
+  end
+end
 
-defmodule Todo.Task do
+defmodule Todo.Task do
   use Ecto.Schema
   use Dx.Ecto.Schema, repo: Todo.Repo
 
-  schema "tasks" do
+  schema "tasks" do
     field :completed_at, :utc_datetime
 
     belongs_to :list, Todo.List
-  end
-end

The inputs are the list to be archived and the current_user who tries to archive it. + end +end

The inputs are the list to be archived and the current_user who tries to archive it. The end result is either the now archived list, or an error.

Without Dx, we'd write a function that takes the list and the user who tries to archive it. We'd then have to think about how to load the data needed to compute the result, and implement it.

With Dx, we don't have to think about how to load the data or compute the result. Instead, we focus entirely on what is relevant and write the rules to represent this logic. @@ -184,39 +184,39 @@

In the code where the outcome is used, f.ex. a web request, call Dx with the main data point and this predicate. We also add additional data needed as args.

In our example:

# in Todo.List
 infer archivable?: :ok
-infer archivable?: {:error, :unauthorized}
-infer archivable?: {:error, :pending_tasks}
+infer archivable?: {:error, :unauthorized}
+infer archivable?: {:error, :pending_tasks}
 
 # in the List controller
-with :ok <- Dx.load!(list, :archivable?, args: [current_user: current_user]),
-     {:ok, archived_list} <- List.archive(list) do
-  render(conn, "show.html", list: archived_list)
-end
  • Flesh out the conditions for the various cases. For each condition, think about what's needed +with :ok <- Dx.load!(list, :archivable?, args: [current_user: current_user]), + {:ok, archived_list} <- List.archive(list) do + render(conn, "show.html", list: archived_list) +end

  • Flesh out the conditions for the various cases. For each condition, think about what's needed and how it could be called. If there's a good answer, use the term in the condition as if it already existed. This way, it's easier to stay on the requirements level, using terms that make sense in the app's domain.

    In our example, we also reverse the order, checking all error cases first, and returning :ok otherwise:

    # in Todo.List
    -infer archivable?: {:error, :unauthorized}, when: %{can_archive?: false}
    -infer archivable?: {:error, :pending_tasks}, when: %{tasks: %{completed?: false}}
    +infer archivable?: {:error, :unauthorized}, when: %{can_archive?: false}
    +infer archivable?: {:error, :pending_tasks}, when: %{tasks: %{completed?: false}}
     infer archivable?: :ok
  • Define the predicates you used on the correct schema types, and continue the process until the requirements are fully defined using rules.

    In our example, the final set of rules might look like this:

    # in Todo.List
    -infer archivable?: {:error, :unauthorized}, when: %{can_archive?: false}
    -infer archivable?: {:error, :pending_tasks}, when: %{tasks: %{completed?: false}}
    +infer archivable?: {:error, :unauthorized}, when: %{can_archive?: false}
    +infer archivable?: {:error, :pending_tasks}, when: %{tasks: %{completed?: false}}
     infer archivable?: :ok
     
    -infer can_archive?: true, when: %{args: %{current_user: %{is_admin?: true}}}
    -infer can_archive?: true, when: %{is_owner?: true}
    +infer can_archive?: true, when: %{args: %{current_user: %{is_admin?: true}}}
    +infer can_archive?: true, when: %{is_owner?: true}
     infer can_archive?: false
     
    -infer is_owner?: true, when: %{created_by_id: {:ref, [:args, :current_user, :id]}}
    +infer is_owner?: true, when: %{created_by_id: {:ref, [:args, :current_user, :id]}}
     infer is_owner?: false
     
     # in Todo.User
    -infer is_admin?: true, when: %{roles: %{name: [:admin, :super_admin]}}
    +infer is_admin?: true, when: %{roles: %{name: [:admin, :super_admin]}}
     infer is_admin?: false
     
     # in Todo.Task
    -infer completed?: true, when: %{completed_at: {:not, nil}}
    +infer completed?: true, when: %{completed_at: {:not, nil}}
     infer completed?: false
  • @@ -225,8 +225,8 @@

    We could use the rules we defined to cover other use cases as well:

    Filtering archivable lists

    Say we have a list of Todo.List structs, and want to keep only the ones that can be archived, for example to implement a web request to archive multiple lists. We use Dx.filter/3 for it, -which takes a list of data as well as a condition, just like the ones we use when defining rules:

    Dx.filter(lists, %{archivable?: :ok}, args: [current_user: current_user])

    Querying all archivable lists

    Say we want to query all lists that a given user can archive. We use Dx.query_all/3 for it, -which takes a type as well as a condition, just like the ones we use when defining rules:

    Dx.query_all(Todo.List, %{archivable?: :ok}, args: [current_user: current_user])
    +which takes a list of data as well as a condition, just like the ones we use when defining rules:

    Dx.filter(lists, %{archivable?: :ok}, args: [current_user: current_user])

    Querying all archivable lists

    Say we want to query all lists that a given user can archive. We use Dx.query_all/3 for it, +which takes a type as well as a condition, just like the ones we use when defining rules:

    Dx.query_all(Todo.List, %{archivable?: :ok}, args: [current_user: current_user])
    diff --git a/99_outro.html b/99_outro.html index fcf7a62c..35f89643 100644 --- a/99_outro.html +++ b/99_outro.html @@ -112,7 +112,7 @@

    - + View Source diff --git a/Dx.Defd.html b/Dx.Defd.html index e7d7d673..ce8049dc 100644 --- a/Dx.Defd.html +++ b/Dx.Defd.html @@ -112,7 +112,7 @@

    - + View Source @@ -218,7 +218,7 @@

    defd(call)

    - + View Source @@ -242,7 +242,7 @@

    defd(call)

    defd(call, list)

    - + View Source @@ -268,7 +268,7 @@

    defd(call, list)

    get(call, opts \\ [])

    - + View Source @@ -294,7 +294,7 @@

    get(call, opts \\ [])

    get!(call, opts \\ [])

    - + View Source @@ -320,7 +320,7 @@

    get!(call, opts \\ [])

    load(call, opts \\ [])

    - + View Source @@ -346,7 +346,7 @@

    load(call, opts \\ [])

    load!(call, opts \\ [])

    - + View Source @@ -370,7 +370,7 @@

    load!(call, opts \\ [])

    non_dx(code)

    - + View Source diff --git a/Dx.Ecto.Query.Batches.html b/Dx.Ecto.Query.Batches.html index b1b2e5cc..ad0849d5 100644 --- a/Dx.Ecto.Query.Batches.html +++ b/Dx.Ecto.Query.Batches.html @@ -112,7 +112,7 @@

    - + View Source @@ -153,52 +153,52 @@

    Examples

    -
    iex> add_filters(:query1, [color: "red", char: "A"])
    -...> |> get_batches()
    -[
    -  query1: [
    -    {:char, ["A"], color: "red"}
    -  ]
    -]
    -
    -iex> add_filters(:query1, [color: "red", char: "A"])
    -...> |> add_filters(:query1, [color: "blue", char: "A"])
    -...> |> get_batches()
    -[
    -  query1: [
    -    {:color, ["blue", "red"], char: "A"}
    -  ]
    -]
    -
    -iex> add_filters(:query1, [color: "red", char: "A", size: "L"])
    -...> |> add_filters(:query1, [color: "blue", size: "S", char: "A"])
    -...> |> add_filters(:query1, [color: "pink", size: "S", char: "B"])
    -...> |> add_filters(:query1, [color: "pink", size: "S", char: "A"])
    -...> |> get_batches()
    -[
    -  query1: [
    -    {:char, ["A"], color: "blue", size: "S"},
    -    {:char, ["A", "B"], color: "pink", size: "S"},
    -    {:char, ["A"], color: "red", size: "L"}
    -  ]
    -]
    -
    -iex> add_filters(:query1, [color: "red"])
    -...> |> add_filters(:query1, [color: "blue"])
    -...> |> get_batches()
    -[
    -  query1: [
    -    {:color, ["blue", "red"], []}
    -  ]
    -]
    -
    -iex> add_filters(:query1, [])
    -...> |> get_batches()
    -[
    -  query1: [
    -    {}
    -  ]
    -]
    +
    iex> add_filters(:query1, [color: "red", char: "A"])
    +...> |> get_batches()
    +[
    +  query1: [
    +    {:char, ["A"], color: "red"}
    +  ]
    +]
    +
    +iex> add_filters(:query1, [color: "red", char: "A"])
    +...> |> add_filters(:query1, [color: "blue", char: "A"])
    +...> |> get_batches()
    +[
    +  query1: [
    +    {:color, ["blue", "red"], char: "A"}
    +  ]
    +]
    +
    +iex> add_filters(:query1, [color: "red", char: "A", size: "L"])
    +...> |> add_filters(:query1, [color: "blue", size: "S", char: "A"])
    +...> |> add_filters(:query1, [color: "pink", size: "S", char: "B"])
    +...> |> add_filters(:query1, [color: "pink", size: "S", char: "A"])
    +...> |> get_batches()
    +[
    +  query1: [
    +    {:char, ["A"], color: "blue", size: "S"},
    +    {:char, ["A", "B"], color: "pink", size: "S"},
    +    {:char, ["A"], color: "red", size: "L"}
    +  ]
    +]
    +
    +iex> add_filters(:query1, [color: "red"])
    +...> |> add_filters(:query1, [color: "blue"])
    +...> |> get_batches()
    +[
    +  query1: [
    +    {:color, ["blue", "red"], []}
    +  ]
    +]
    +
    +iex> add_filters(:query1, [])
    +...> |> get_batches()
    +[
    +  query1: [
    +    {}
    +  ]
    +]
    @@ -279,7 +279,7 @@

    add_filters(state \\ %{}, group, filters)

    - + View Source @@ -302,7 +302,7 @@

    add_filters(state \\ %{}, group, filters)

    get_batches(state)

    - + View Source @@ -319,13 +319,13 @@

    get_batches(state)

    Format

    -
    [
    -  group1: [
    -    {:batch_key, list_of_values, other_filters_keyword},
    +
    [
    +  group1: [
    +    {:batch_key, list_of_values, other_filters_keyword},
         # ...
    -  ],
    +  ],
       # ...
    -]
    +
    ]
    @@ -337,7 +337,7 @@

    get_batches(state)

    map_put_in(map, list, value)

    - + View Source @@ -359,7 +359,7 @@

    map_put_in(map, list, value)

    new()

    - + View Source diff --git a/Dx.Ecto.Query.TranslationError.html b/Dx.Ecto.Query.TranslationError.html index 90b75484..98a37aca 100644 --- a/Dx.Ecto.Query.TranslationError.html +++ b/Dx.Ecto.Query.TranslationError.html @@ -112,7 +112,7 @@

    - + View Source @@ -169,7 +169,7 @@

    message(e)

    - + View Source diff --git a/Dx.Ecto.Schema.html b/Dx.Ecto.Schema.html index 769adda6..d59559da 100644 --- a/Dx.Ecto.Schema.html +++ b/Dx.Ecto.Schema.html @@ -112,7 +112,7 @@

    - + View Source diff --git a/Dx.Error.Generic.html b/Dx.Error.Generic.html index 2a6108b1..731bb635 100644 --- a/Dx.Error.Generic.html +++ b/Dx.Error.Generic.html @@ -112,7 +112,7 @@

    - + View Source @@ -169,7 +169,7 @@

    message(error)

    - + View Source diff --git a/Dx.Error.NotLoaded.html b/Dx.Error.NotLoaded.html index 8623f3ca..fa3618ac 100644 --- a/Dx.Error.NotLoaded.html +++ b/Dx.Error.NotLoaded.html @@ -112,7 +112,7 @@

    - + View Source @@ -169,7 +169,7 @@

    message(error)

    - + View Source diff --git a/Dx.Error.RulesNotFound.html b/Dx.Error.RulesNotFound.html index dad25127..a36787fb 100644 --- a/Dx.Error.RulesNotFound.html +++ b/Dx.Error.RulesNotFound.html @@ -112,7 +112,7 @@

    - + View Source @@ -169,7 +169,7 @@

    message(error)

    - + View Source diff --git a/Dx.Error.Timeout.html b/Dx.Error.Timeout.html index af94897f..ebc3ab62 100644 --- a/Dx.Error.Timeout.html +++ b/Dx.Error.Timeout.html @@ -112,7 +112,7 @@

    - + View Source @@ -169,7 +169,7 @@

    message(error)

    - + View Source diff --git a/Dx.Result.html b/Dx.Result.html index b124a419..72dd3a5d 100644 --- a/Dx.Result.html +++ b/Dx.Result.html @@ -112,7 +112,7 @@

    - + View Source @@ -136,13 +136,13 @@

    Example using all

    -

    For example, using all?/1 with 3 conditions A, B and C, where

    iex> [
    -...>   {:ok, true, %{}},   # A
    -...>   {:not_loaded, [1]}, # B
    -...>   {:ok, false, %{}},  # C
    -...> ]
    -...> |> Dx.Result.all?()
    -{:ok, false, %{}}

    The overall result is {:ok, false, %{}}. +

    For example, using all?/1 with 3 conditions A, B and C, where

    iex> [
    +...>   {:ok, true, %{}},   # A
    +...>   {:not_loaded, [1]}, # B
    +...>   {:ok, false, %{}},  # C
    +...> ]
    +...> |> Dx.Result.all?()
    +{:ok, false, %{}}

    The overall result is {:ok, false, %{}}. While B would need more data to be loaded, C can already determined and is false, so and any additional data loaded will not change that.

    @@ -150,15 +150,15 @@

    Example using find

    -

    Another example, using find/1 with 5 conditions A, B, C, D and E, where

    iex> [
    -...>   {:ok, false, %{}},  # A
    -...>   {:not_loaded, [1]}, # B
    -...>   {:not_loaded, [2]}, # C
    -...>   {:ok, true, %{}},   # D
    -...>   {:not_loaded, [3]}, # E
    -...> ]
    -...> |> Dx.Result.find()
    -{:not_loaded, [1, 2]}

    The overall result is {:not_loaded, data_reqs1 + data_reqs2}. +

    Another example, using find/1 with 5 conditions A, B, C, D and E, where

    iex> [
    +...>   {:ok, false, %{}},  # A
    +...>   {:not_loaded, [1]}, # B
    +...>   {:not_loaded, [2]}, # C
    +...>   {:ok, true, %{}},   # D
    +...>   {:not_loaded, [3]}, # E
    +...> ]
    +...> |> Dx.Result.find()
    +{:not_loaded, [1, 2]}

    The overall result is {:not_loaded, data_reqs1 + data_reqs2}. While D can already be determined and is {:ok, true, %{}}, B and C come first and need more data to be loaded, so they can be determined and returned if either is {:ok, true, %{}} first. All data requirements that might be needed are returned together in the result (those of B and C), @@ -429,7 +429,7 @@

    b()

    - + View Source @@ -457,7 +457,7 @@

    b()

    binds()

    - + View Source @@ -485,7 +485,7 @@

    binds()

    v()

    - + View Source @@ -527,7 +527,7 @@

    all?(enum, mapper \\ &identity/1)

    - + View Source @@ -551,28 +551,28 @@

    all?(enum, mapper \\ &identity/1)

    Examples

    -
    iex> [
    -...>   {:ok, true, %{}},
    -...>   {:not_loaded, []},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.all?()
    -{:ok, false, %{}}
    -
    -iex> [
    -...>   {:ok, true, %{}},
    -...>   {:not_loaded, []},
    -...>   {:ok, true, %{}},
    -...> ]
    -...> |> Dx.Result.all?()
    -{:not_loaded, []}
    -
    -iex> [
    -...>   {:ok, true, %{}},
    -...>   {:ok, true, %{}},
    -...> ]
    -...> |> Dx.Result.all?()
    -{:ok, true, %{}}
    +
    iex> [
    +...>   {:ok, true, %{}},
    +...>   {:not_loaded, []},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.all?()
    +{:ok, false, %{}}
    +
    +iex> [
    +...>   {:ok, true, %{}},
    +...>   {:not_loaded, []},
    +...>   {:ok, true, %{}},
    +...> ]
    +...> |> Dx.Result.all?()
    +{:not_loaded, []}
    +
    +iex> [
    +...>   {:ok, true, %{}},
    +...>   {:ok, true, %{}},
    +...> ]
    +...> |> Dx.Result.all?()
    +{:ok, true, %{}}
    @@ -586,7 +586,7 @@

    all?(enum, mapper \\ &identity/1)

    any?(enum, mapper \\ &identity/1)

    - + View Source @@ -610,28 +610,28 @@

    any?(enum, mapper \\ &identity/1)

    Examples

    -
    iex> [
    -...>   {:ok, true, %{a: 1}},
    -...>   {:not_loaded, []},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.any?()
    -{:ok, true, %{a: 1}}
    -
    -iex> [
    -...>   {:ok, false, %{}},
    -...>   {:not_loaded, []},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.any?()
    -{:not_loaded, []}
    -
    -iex> [
    -...>   {:ok, false, %{}},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.any?()
    -{:ok, false, %{}}
    +
    iex> [
    +...>   {:ok, true, %{a: 1}},
    +...>   {:not_loaded, []},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.any?()
    +{:ok, true, %{a: 1}}
    +
    +iex> [
    +...>   {:ok, false, %{}},
    +...>   {:not_loaded, []},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.any?()
    +{:not_loaded, []}
    +
    +iex> [
    +...>   {:ok, false, %{}},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.any?()
    +{:ok, false, %{}}
    @@ -643,7 +643,7 @@

    any?(enum, mapper \\ &identity/1)

    bind(other, key, val)

    - + View Source @@ -668,7 +668,7 @@

    bind(other, key, val)

    count(enum, fun \\ &identity/1)

    - + View Source @@ -692,39 +692,39 @@

    count(enum, fun \\ &identity/1)

    Examples -
    iex> [
    -...>   {:ok, true, %{}},
    -...>   {:ok, false, %{}},
    -...>   {:ok, true, %{}},
    -...> ]
    -...> |> Dx.Result.count()
    -{:ok, 2, %{}}
    -
    -iex> [
    -...>   {:ok, false, %{}},
    -...>   {:not_loaded, [1]},
    -...>   {:not_loaded, [2]},
    -...>   {:ok, true, %{}},
    -...>   {:not_loaded, [3]},
    -...> ]
    -...> |> Dx.Result.count()
    -{:not_loaded, [1, 2, 3]}
    -
    -iex> [
    -...>   {:ok, true, %{}},
    -...>   {:ok, :skip, %{}},
    -...>   {:ok, false, %{}},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.count()
    -{:ok, 1, %{}}
    -
    -iex> [
    +
    iex> [
    +...>   {:ok, true, %{}},
    +...>   {:ok, false, %{}},
    +...>   {:ok, true, %{}},
    +...> ]
    +...> |> Dx.Result.count()
    +{:ok, 2, %{}}
    +
    +iex> [
    +...>   {:ok, false, %{}},
    +...>   {:not_loaded, [1]},
    +...>   {:not_loaded, [2]},
    +...>   {:ok, true, %{}},
    +...>   {:not_loaded, [3]},
    +...> ]
    +...> |> Dx.Result.count()
    +{:not_loaded, [1, 2, 3]}
    +
    +iex> [
    +...>   {:ok, true, %{}},
    +...>   {:ok, :skip, %{}},
    +...>   {:ok, false, %{}},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.count()
    +{:ok, 1, %{}}
    +
    +iex> [
     ...>   false,
     ...>   false,
    -...> ]
    -...> |> Dx.Result.count(&{:ok, not &1, %{}})
    -{:ok, 2, %{}}
    +
    ...> ] +...> |> Dx.Result.count(&{:ok, not &1, %{}}) +{:ok, 2, %{}}
    @@ -738,7 +738,7 @@

    count(enum, fun \\ &identity/1)

    count_while(enum, fun \\ &identity/1)

    - + View Source @@ -763,39 +763,39 @@

    count_while(enum, fun \\ &identity/1) Examples

    -
    iex> [
    -...>   {:ok, true, %{}},
    -...>   {:not_loaded, []},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.count_while()
    -{:not_loaded, []}
    -
    -iex> [
    -...>   {:ok, false, %{}},
    -...>   {:not_loaded, [1]},
    -...>   {:not_loaded, [2]},
    -...>   {:ok, true, %{}},
    -...>   {:not_loaded, [3]},
    -...> ]
    -...> |> Dx.Result.count_while()
    -{:ok, 0, %{}}
    -
    -iex> [
    -...>   {:ok, true, %{}},
    -...>   {:ok, :skip, %{}},
    -...>   {:ok, false, %{}},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.count_while()
    -{:ok, 1, %{}}
    -
    -iex> [
    +
    iex> [
    +...>   {:ok, true, %{}},
    +...>   {:not_loaded, []},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.count_while()
    +{:not_loaded, []}
    +
    +iex> [
    +...>   {:ok, false, %{}},
    +...>   {:not_loaded, [1]},
    +...>   {:not_loaded, [2]},
    +...>   {:ok, true, %{}},
    +...>   {:not_loaded, [3]},
    +...> ]
    +...> |> Dx.Result.count_while()
    +{:ok, 0, %{}}
    +
    +iex> [
    +...>   {:ok, true, %{}},
    +...>   {:ok, :skip, %{}},
    +...>   {:ok, false, %{}},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.count_while()
    +{:ok, 1, %{}}
    +
    +iex> [
     ...>   false,
     ...>   false,
    -...> ]
    -...> |> Dx.Result.count_while(&{:ok, not &1, %{}})
    -{:ok, 2, %{}}
    +
    ...> ] +...> |> Dx.Result.count_while(&{:ok, not &1, %{}}) +{:ok, 2, %{}}
    @@ -811,7 +811,7 @@

    count_while(enum, fun \\ &identity/1)

    filter_map(enum, fun \\ &identity/1, result_mapper \\ &ok/2)

    - + View Source @@ -839,7 +839,7 @@

    filter_map(enum, fun \\ &identity/1, re

    find(enum, fun \\ &identity/1, result_mapper \\ &ok/2, default \\ ok(nil))

    - + View Source @@ -863,37 +863,37 @@

    find(enum, fun \\ &identity/1, result_m Examples

    -
    iex> [
    -...>   {:ok, true, %{}},
    -...>   {:not_loaded, []},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.find()
    -{:ok, {:ok, true, %{}}, %{}}
    -
    -iex> [
    -...>   {:ok, false, %{}},
    -...>   {:not_loaded, [1]},
    -...>   {:not_loaded, [2]},
    -...>   {:ok, true, %{}},
    -...>   {:not_loaded, [3]},
    -...> ]
    -...> |> Dx.Result.find()
    -{:not_loaded, [1, 2]}
    -
    -iex> [
    -...>   {:ok, false, %{}},
    -...>   {:ok, false, %{}},
    -...> ]
    -...> |> Dx.Result.find()
    -{:ok, nil, %{}}
    -
    -iex> [
    +
    iex> [
    +...>   {:ok, true, %{}},
    +...>   {:not_loaded, []},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.find()
    +{:ok, {:ok, true, %{}}, %{}}
    +
    +iex> [
    +...>   {:ok, false, %{}},
    +...>   {:not_loaded, [1]},
    +...>   {:not_loaded, [2]},
    +...>   {:ok, true, %{}},
    +...>   {:not_loaded, [3]},
    +...> ]
    +...> |> Dx.Result.find()
    +{:not_loaded, [1, 2]}
    +
    +iex> [
    +...>   {:ok, false, %{}},
    +...>   {:ok, false, %{}},
    +...> ]
    +...> |> Dx.Result.find()
    +{:ok, nil, %{}}
    +
    +iex> [
     ...>   false,
     ...>   false,
    -...> ]
    -...> |> Dx.Result.find(&{:ok, not &1, %{}})
    -{:ok, false, %{}}
    +
    ...> ] +...> |> Dx.Result.find(&{:ok, not &1, %{}}) +{:ok, false, %{}}
    @@ -905,7 +905,7 @@

    find(enum, fun \\ &identity/1, result_m

    from_simple(other)

    - + View Source @@ -921,13 +921,13 @@

    from_simple(other)

    Examples -
    iex> {:ok, 5}
    -...> |>Dx.Result.from_simple()
    -{:ok, 5, %{}}
    +
    iex> {:ok, 5}
    +...> |>Dx.Result.from_simple()
    +{:ok, 5, %{}}
     
    -iex> {:error, :err}
    -...> |>Dx.Result.from_simple()
    -{:error, :err}
    +
    iex> {:error, :err} +...> |>Dx.Result.from_simple() +{:error, :err}
    @@ -941,7 +941,7 @@

    from_simple(other)

    map(enum, mapper \\ &identity/1)

    - + View Source @@ -964,31 +964,31 @@

    map(enum, mapper \\ &identity/1)

    Examples -
    iex> [
    -...>   {:ok, 1, %{}},
    -...>   {:ok, 2, %{}},
    -...>   {:ok, 3, %{}},
    -...> ]
    -...> |> Dx.Result.map()
    -{:ok, [1, 2, 3], %{}}
    -
    -iex> [
    -...>   {:ok, 1, %{}},
    -...>   {:not_loaded, [:x]},
    -...>   {:ok, 3, %{}},
    -...>   {:not_loaded, [:y]},
    -...> ]
    -...> |> Dx.Result.map()
    -{:not_loaded, [:x, :y]}
    -
    -iex> [
    -...>   {:ok, 1, %{}},
    -...>   {:error, :x},
    -...>   {:ok, 3, %{}},
    -...>   {:not_loaded, [:y]},
    -...> ]
    -...> |> Dx.Result.map()
    -{:error, :x}
    +
    iex> [
    +...>   {:ok, 1, %{}},
    +...>   {:ok, 2, %{}},
    +...>   {:ok, 3, %{}},
    +...> ]
    +...> |> Dx.Result.map()
    +{:ok, [1, 2, 3], %{}}
    +
    +iex> [
    +...>   {:ok, 1, %{}},
    +...>   {:not_loaded, [:x]},
    +...>   {:ok, 3, %{}},
    +...>   {:not_loaded, [:y]},
    +...> ]
    +...> |> Dx.Result.map()
    +{:not_loaded, [:x, :y]}
    +
    +iex> [
    +...>   {:ok, 1, %{}},
    +...>   {:error, :x},
    +...>   {:ok, 3, %{}},
    +...>   {:not_loaded, [:y]},
    +...> ]
    +...> |> Dx.Result.map()
    +{:error, :x}
    @@ -1002,7 +1002,7 @@

    map(enum, mapper \\ &identity/1)

    map_keyword_values(enum, mapper \\ &identity/1)

    - + View Source @@ -1019,31 +1019,31 @@

    map_keyword_values(enum, mapper \\ &ide Examples

    -
    iex> [
    -...>   a: {:ok, 1, %{}},
    -...>   b: {:ok, 2, %{}},
    -...>   c: {:ok, 3, %{}},
    -...> ]
    -...> |> Dx.Result.map_keyword_values()
    -{:ok, [a: 1, b: 2, c: 3], %{}}
    -
    -iex> [
    -...>   a: {:ok, 1, %{}},
    -...>   b: {:not_loaded, MapSet.new([:x])},
    -...>   c: {:ok, 3, %{}},
    -...>   d: {:not_loaded, MapSet.new([:y])},
    -...> ]
    -...> |> Dx.Result.map_keyword_values()
    -{:not_loaded, MapSet.new([:x, :y])}
    -
    -iex> [
    -...>   a: {:ok, 1, %{}},
    -...>   b: {:error, :x},
    -...>   c: {:ok, 3, %{}},
    -...>   d: {:not_loaded, [:y]},
    -...> ]
    -...> |> Dx.Result.map_keyword_values()
    -{:error, :x}
    +
    iex> [
    +...>   a: {:ok, 1, %{}},
    +...>   b: {:ok, 2, %{}},
    +...>   c: {:ok, 3, %{}},
    +...> ]
    +...> |> Dx.Result.map_keyword_values()
    +{:ok, [a: 1, b: 2, c: 3], %{}}
    +
    +iex> [
    +...>   a: {:ok, 1, %{}},
    +...>   b: {:not_loaded, MapSet.new([:x])},
    +...>   c: {:ok, 3, %{}},
    +...>   d: {:not_loaded, MapSet.new([:y])},
    +...> ]
    +...> |> Dx.Result.map_keyword_values()
    +{:not_loaded, MapSet.new([:x, :y])}
    +
    +iex> [
    +...>   a: {:ok, 1, %{}},
    +...>   b: {:error, :x},
    +...>   c: {:ok, 3, %{}},
    +...>   d: {:not_loaded, [:y]},
    +...> ]
    +...> |> Dx.Result.map_keyword_values()
    +{:error, :x}
    @@ -1057,7 +1057,7 @@

    map_keyword_values(enum, mapper \\ &ide

    map_values(enum, mapper \\ &identity/1)

    - + View Source @@ -1074,31 +1074,31 @@

    map_values(enum, mapper \\ &identity/1) Examples

    -
    iex> %{
    -...>   a: {:ok, 1, %{}},
    -...>   b: {:ok, 2, %{}},
    -...>   c: {:ok, 3, %{}},
    -...> }
    -...> |> Dx.Result.map_values()
    -{:ok, %{a: 1, b: 2, c: 3}, %{}}
    -
    -iex> %{
    -...>   a: {:ok, 1, %{}},
    -...>   b: {:not_loaded, MapSet.new([:x])},
    -...>   c: {:ok, 3, %{}},
    -...>   d: {:not_loaded, MapSet.new([:y])},
    -...> }
    -...> |> Dx.Result.map_values()
    -{:not_loaded, MapSet.new([:x, :y])}
    -
    -iex> %{
    -...>   a: {:ok, 1, %{}},
    -...>   b: {:error, :x},
    -...>   c: {:ok, 3, %{}},
    -...>   d: {:not_loaded, [:y]},
    -...> }
    -...> |> Dx.Result.map_values()
    -{:error, :x}
    +
    iex> %{
    +...>   a: {:ok, 1, %{}},
    +...>   b: {:ok, 2, %{}},
    +...>   c: {:ok, 3, %{}},
    +...> }
    +...> |> Dx.Result.map_values()
    +{:ok, %{a: 1, b: 2, c: 3}, %{}}
    +
    +iex> %{
    +...>   a: {:ok, 1, %{}},
    +...>   b: {:not_loaded, MapSet.new([:x])},
    +...>   c: {:ok, 3, %{}},
    +...>   d: {:not_loaded, MapSet.new([:y])},
    +...> }
    +...> |> Dx.Result.map_values()
    +{:not_loaded, MapSet.new([:x, :y])}
    +
    +iex> %{
    +...>   a: {:ok, 1, %{}},
    +...>   b: {:error, :x},
    +...>   c: {:ok, 3, %{}},
    +...>   d: {:not_loaded, [:y]},
    +...> }
    +...> |> Dx.Result.map_values()
    +{:error, :x}
    @@ -1112,7 +1112,7 @@

    map_values(enum, mapper \\ &identity/1)

    ok(value, binds \\ %{})

    - + View Source @@ -1134,7 +1134,7 @@

    ok(value, binds \\ %{})

    then(other, fun)

    - + View Source @@ -1157,7 +1157,7 @@

    then(other, fun)

    to_simple(other)

    - + View Source @@ -1173,13 +1173,13 @@

    to_simple(other)

    Examples -
    iex> {:ok, 5, %{}}
    -...> |>Dx.Result.to_simple()
    -{:ok, 5}
    +
    iex> {:ok, 5, %{}}
    +...> |>Dx.Result.to_simple()
    +{:ok, 5}
     
    -iex> {:error, :err}
    -...> |>Dx.Result.to_simple()
    -{:error, :err}
    +
    iex> {:error, :err} +...> |>Dx.Result.to_simple() +{:error, :err}
    @@ -1191,7 +1191,7 @@

    to_simple(other)

    to_simple_if(other, arg2)

    - + View Source @@ -1213,7 +1213,7 @@

    to_simple_if(other, arg2)

    transform(other, fun)

    - + View Source @@ -1236,7 +1236,7 @@

    transform(other, fun)

    unwrap!(arg)

    - + View Source @@ -1253,10 +1253,10 @@

    unwrap!(arg)

    Examples -
    iex> Dx.Result.unwrap!({:error, %ArgumentError{}})
    +
    iex> Dx.Result.unwrap!({:error, %ArgumentError{}})
     ** (ArgumentError) argument error
     
    -iex> Dx.Result.unwrap!({:error, :not_an_exception})
    +iex> Dx.Result.unwrap!({:error, :not_an_exception})
     ** (Dx.Error.Generic) Error occurred: :not_an_exception
    @@ -1269,7 +1269,7 @@

    unwrap!(arg)

    wrap(term)

    - + View Source diff --git a/Dx.Scope.html b/Dx.Scope.html index 0e0a4f64..b9f8251d 100644 --- a/Dx.Scope.html +++ b/Dx.Scope.html @@ -112,7 +112,7 @@

    - + View Source @@ -245,7 +245,7 @@

    add_conditions(scope, ext_ok_fun)

    - + View Source @@ -267,7 +267,7 @@

    add_conditions(scope, ext_ok_fun)

    all(module)

    - + View Source @@ -289,7 +289,7 @@

    all(module)

    extract_main_condition_candidates(scope)

    - + View Source @@ -311,7 +311,7 @@

    extract_main_condition_candidates(scope)

    lookup(scope, eval)

    - + View Source @@ -333,7 +333,7 @@

    lookup(scope, eval)

    main_condition_candidates(other)

    - + View Source @@ -355,7 +355,7 @@

    main_condition_candidates(other)

    map_plan(scope, fun)

    - + View Source @@ -377,7 +377,7 @@

    map_plan(scope, fun)

    maybe_atom(atom)

    - + View Source @@ -399,7 +399,7 @@

    maybe_atom(atom)

    maybe_load(other, eval)

    - + View Source @@ -421,7 +421,7 @@

    maybe_load(other, eval)

    maybe_lookup(scope, eval)

    - + View Source @@ -443,7 +443,7 @@

    maybe_lookup(scope, eval)

    to_data_req(scope)

    - + View Source diff --git a/Dx.html b/Dx.html index 169468db..eb99fe21 100644 --- a/Dx.html +++ b/Dx.html @@ -112,7 +112,7 @@

    - + View Source @@ -130,7 +130,7 @@

    Passing an individual subject will return the predicates for the subject, passing a list will return a list of them.
  • predicates can either be a single predicate, or a list of predicates. Passing a single predicate will return the resulting value, passing a list will return a Map of the predicates and their resulting values.
  • options (optional) See below.
  • Options:

    Options:

    • args (list or map) can be used to pass in data from the caller's context that can be used in -rules (see Arguments below). A classic example is the current_user, e.g.
      Dx.put!(project, :can_edit?, args: [user: current_user])
    • extra_rules (module or list of modules) can be used to add context-specific rules that are +rules (see Arguments below). A classic example is the current_user, e.g.
      Dx.put!(project, :can_edit?, args: [user: current_user])
    • extra_rules (module or list of modules) can be used to add context-specific rules that are not defined directly on the subject. This can be used to structure rules into their own modules and use them only where needed.
    • debug? (boolean) makes Dx print additional information to the console as rules are evaluated. Should only be used while debugging.

    @@ -165,16 +165,16 @@

    Conditions

    In a rule condition, the part after when: ...,

    • Maps represent multiple conditions, of which all need to be satisfied (logical AND).
    • Lists represent multiple conditions, of which at least one needs to be satisfied (logical OR).
    • Values can be negated using {:not, "value"}.

    Examples:

    # :role must be "admin"
    -infer role: :admin, when: %{role: "admin"}
    +infer role: :admin, when: %{role: "admin"}
     
     # :role must be either "admin" or "superadmin"
    -infer role: :admin, when: %{role: ["admin", "superadmin"]}
    +infer role: :admin, when: %{role: ["admin", "superadmin"]}
     
     # :role must be "admin" and :verified? must be true
    -infer role: :admin, when: %{role: "admin", verified?: true}
    +infer role: :admin, when: %{role: "admin", verified?: true}
     
     # :role must be "admin" and :verified_at must not be nil
    -infer role: :admin, when: %{role: "admin", verified_at: {:not, nil}}

    +infer role: :admin, when: %{role: "admin", verified_at: {:not, nil}}

    @@ -189,15 +189,15 @@

    When conditions are tested against list data, e.g. a person's list of roles, the condition is satisfied if at least one element of the list matches the given conditions (like Enum.any?/2).

    Although they might look similar, it's important to differentiate between lists that appear in conditions, and lists that appear in the data, which are checked against a condition.

    When both occur together, i.e. a list in a condition is checked against a list of values, the condition -is met if at least one of the condition list elements applies to at least one element of the value list.

    For example:

    infer :can_edit?, when: %{roles: ["project_manager", "admin"]}
    +is met if at least one of the condition list elements applies to at least one element of the value list.

    For example:

    infer :can_edit?, when: %{roles: ["project_manager", "admin"]}
     
    -iex> %Person{roles: ["worker", "assistant"]} |> Dx.get!(:can_edit?)
    +iex> %Person{roles: ["worker", "assistant"]} |> Dx.get!(:can_edit?)
     nil
     
    -iex> %Person{roles: ["assistant", "project_manager"]} |> Dx.get!(:can_edit?)
    +iex> %Person{roles: ["assistant", "project_manager"]} |> Dx.get!(:can_edit?)
     true
     
    -iex> %Person{roles: ["admin"]} |> Dx.get!(:can_edit?)
    +iex> %Person{roles: ["admin"]} |> Dx.get!(:can_edit?)
     true

    The same applies to complex conditions.

    @@ -205,7 +205,7 @@

    Rule results

    The assigned value of a predicate is generally assigned as is.

    A few special tuples, however, will be replaced by Dx (see Features below)

    Example:

    infer d: 4
    -infer nested: %{a: 1, b: 2, c: {:ref, :d}}  # => %{a: 1, b: 2, c: 4}

    +infer nested: %{a: 1, b: 2, c: {:ref, :d}} # => %{a: 1, b: 2, c: 4}

    @@ -213,14 +213,14 @@

    Syntax:

    • {:ref, path} (in conditions and result values)

    Arguments:

    • path is a list of fields or predicates, starting from the subject. The brackets can be omitted (i.a. an atom passed), if the path consists of one element. -The last element can be a map or list (see Branching below)

    Example:

    infer ot_fields: %{editable: true},
    -    when: %{
    +The last element can be a map or list (see Branching below)

    Example:

    infer ot_fields: %{editable: true},
    +    when: %{
           construction_bectu?: true,
    -      roles: %{
    -        user: {:ref, [:args, :user]},
    -        type: ["project_manager", "admin"]
    -      }
    -    }

    + roles: %{ + user: {:ref, [:args, :user]}, + type: ["project_manager", "admin"] + } + }

    @@ -231,11 +231,11 @@

    It basically behaves similar to Enum.map/2.

    A map as last element of a path will branch the returned result out into this map. The keys are returned as is, the values must be a list (or atom) continuing that path. This is particularly powerful when used on a list of subjects (see above), because it -will return the given map with the values at the given paths for each underlying subject:

    A list as last element of a path behaves like a map where each value equals its key.

    Examples:

    infer list: [%{a: 1, b: 2, c: %{d: 4}}, %{a: 9, b: 8, c: %{d: 6}}]
    +will return the given map with the values at the given paths for each underlying subject:

    A list as last element of a path behaves like a map where each value equals its key.

    Examples:

    infer list: [%{a: 1, b: 2, c: %{d: 4}}, %{a: 9, b: 8, c: %{d: 6}}]
     
    -infer result1: {:ref, [:list, :a]}  # => [1, 9]
    -infer result2: {:ref, [:list, %{x: :a, y: [:c, :d]}]}  # => [%{x: 1, y: 4}, %{x: 9, y: 6}]
    -infer result3: {:ref, [:list, [:a, :b]]}  # => [%{a: 1, b: 2}, %{a: 9, b: 8}]

    +infer result1: {:ref, [:list, :a]} # => [1, 9] +infer result2: {:ref, [:list, %{x: :a, y: [:c, :d]}]} # => [%{x: 1, y: 4}, %{x: 9, y: 6}] +infer result3: {:ref, [:list, [:a, :b]]} # => [%{a: 1, b: 2}, %{a: 9, b: 8}]

    @@ -250,15 +250,15 @@

    It's possible to give predicates the same name as existing fields in the schema. This represents the fact that these fields are derived from other data, using rules.

    Rules on these fields can even take into account the existing value of the underlying field. -In order to reference it, use :fields in between a path or condition, for example:

    schema "blog_posts" do
    +In order to reference it, use :fields in between a path or condition, for example:

    schema "blog_posts" do
       field :state
       field :published_at
    -end
    +end
     
     # nilify published_at when deleted, or when it's an old archived post
    -infer published_at: nil, when: %{state: "deleted"}
    -infer published_at: nil, when: %{state: "archived", fields: %{published_at: {:before, ~D[2020-02-20]}}}
    -infer published_at: {:ref, [:fields, :published_at]}

    While it's always possible to achieve a similar behavior by giving the predicate a different +infer published_at: nil, when: %{state: "deleted"} +infer published_at: nil, when: %{state: "archived", fields: %{published_at: {:before, ~D[2020-02-20]}}} +infer published_at: {:ref, [:fields, :published_at]}

    While it's always possible to achieve a similar behavior by giving the predicate a different name than the field, and then mapping the predicate to the field somewhere else, using the field name in conjunction with :fields makes explicit that it's a conditional override.

    @@ -268,17 +268,17 @@

    Syntax:

    • {:bind, key} (in conditions)
    • {:bind, key, subcondition} (in conditions)
    • {:bound, key} (in result values)
    • {:bound, key, default} (in result values)

    When a condition is evaluated on a list of values, the first value satisfying the condition can be bound to a variable using {:bind, variable}.

    These bound values can be referenced using {:bound, key} with an optional default: -{:bound, key, default}.

    infer project_manager: {:bound, :person},
    -    when: %{roles: %{type: "project_manager", person: {:bind, :person}}}

    +{:bound, key, default}.

    infer project_manager: {:bound, :person},
    +    when: %{roles: %{type: "project_manager", person: {:bind, :person}}}

    Local aliases

    Syntax:

    • infer_alias key: ... (in modules before using key in infer ...)

    In order to create shorthands and avoid repetition, aliases can be defined. -These apply only to subsequent rules within the same module and are not exposed in any other way.

    infer_alias pm?: %{roles: %{type: ["project_manager", admin]}}
    +These apply only to subsequent rules within the same module and are not exposed in any other way.

    infer_alias pm?: %{roles: %{type: ["project_manager", admin]}}
     
    -infer ot_fields: %{editable: true}, when: [:pm?, %{construction_bectu?: true}]

    +infer ot_fields: %{editable: true}, when: [:pm?, %{construction_bectu?: true}]

    @@ -286,9 +286,9 @@

    Syntax:

    • {&module.fun/n, [arg_1, ..., arg_n]} (in result values)
    • {&module.fun/1, arg_1} (in result values)

    Any function can be called to map the given arguments to other values. The function arguments must be passed as a list, except if it's only one. -Arguments can be fixed values or other Dx features (passed as is), such as references.

    infer day_of_week: {&Date.day_of_week/1, {:ref, :date}}
    +Arguments can be fixed values or other Dx features (passed as is), such as references.

    infer day_of_week: {&Date.day_of_week/1, {:ref, :date}}
     
    -infer duration: {&Timex.diff/3, [{:ref, :start_datetime}, {:ref, :end_datetime}, :hours]}

    Only pure functions with low overhead should be used. +infer duration: {&Timex.diff/3, [{:ref, :start_datetime}, {:ref, :end_datetime}, :hours]}

    Only pure functions with low overhead should be used. Dx might call them very often during evaluation (once after each loading of data).

    @@ -314,13 +314,13 @@

    subject of the rule, not the list element. The list element is referenced using the middle arg, which can be either:
    • a bind_key (atom) - the current list element is referenced via {:bound, bind_key} in the mapper
    • a condition - any values bound in the condition via {:bind, key, ...} can be accessed via {:bound, key} in the mapper

    Use the special form of :map only when you need to reference both the list element (via :bound), and the subject of the rule (via :ref). -Using a combination of :filter and basic :map instead is always preferred, if possible.

    Any nil elements in the list are mapped to nil, when using :map without condition.

    Examples:

    infer accepted_offers: {:filter, :offers, %{state: "accepted"}}
    +Using a combination of :filter and basic :map instead is always preferred, if possible.

    Any nil elements in the list are mapped to nil, when using :map without condition.

    Examples:

    infer accepted_offers: {:filter, :offers, %{state: "accepted"}}
     
    -infer offer_ids: {:map, :offers, :id}
    +infer offer_ids: {:map, :offers, :id}
     
     infer first_offer_of_same_user:
    -        {:map, :offers, %{state: "accepted", user_id: {:bind, :uid, {:not, nil}}},
    -         {:query_first, Offer, user_id: {:bound, :uid}, project_id: {:ref, :project_id}}}

    + {:map, :offers, %{state: "accepted", user_id: {:bind, :uid, {:not, nil}}}, + {:query_first, Offer, user_id: {:bound, :uid}, project_id: {:ref, :project_id}}}

    diff --git a/welcome.html b/welcome.html index 397a66d9..e7fa998f 100644 --- a/welcome.html +++ b/welcome.html @@ -112,7 +112,7 @@