Run this notebook

Use Livebook to open this notebook and explore new ideas.

It is easy to get started, on your machine or the cloud.

Click below to open and run it in your Livebook at .

(or change your Livebook location)

<!-- livebook:{"file_entries":[{"name":"dx_demo_task_lists_v1.sqlite","type":"url","url":"https://s3.eu-central-1.amazonaws.com/elixir-dx-demo/dx_demo_task_lists_v1.sqlite"}]} --> # Dx Demo ```elixir Mix.install( [ {:dx, "~> 0.3.0"}, {:kino, "~> 0.11"}, {:ecto_dbg, "~> 0.4"}, {:ecto_erd, "~> 0.5"}, {:ecto_sqlite3, "~> 0.13"} ], config: [dx: [repo: Repo]] ) Logger.configure(level: :warning) ``` ## Repo setup ```elixir defmodule Repo do use Ecto.Repo, otp_app: :demo, adapter: Ecto.Adapters.SQLite3, database: Kino.FS.file_path("dx_demo_task_lists_v1.sqlite") end ``` ```elixir defmodule Repo.QueryLogger do require Logger def handle_event([:repo, :query], _measurements, metadata, _config) do sql = EctoDbg.inline_params(metadata.query, metadata.params, metadata.repo.__adapter__()) case SqlFmt.format_query(sql) do {:ok, formatted} -> IO.puts(formatted <> "\n\n") _else -> :ok end end end :telemetry.detach("ecto-queries") :ok = :telemetry.attach("ecto-queries", [:repo, :query], &Repo.QueryLogger.handle_event/4, nil) ``` ```elixir {:ok, conn} = Kino.start_child({Repo, database: Kino.FS.file_path("dx_demo_task_lists_v1.sqlite")}) Repo.query!("PRAGMA table_list") ``` ## Data schema ````elixir defmodule Schema.User do use Ecto.Schema use Dx.Ecto.Schema, repo: Repo schema "users" do field(:email, :string) field(:verified_at, :utc_datetime) field(:first_name, :string) field(:last_name, :string) has_many(:lists, Schema.List, foreign_key: :created_by_id) belongs_to(:role, Schema.Role) end end defmodule Schema.Role do use Ecto.Schema use Dx.Ecto.Schema, repo: Repo schema "roles" do field(:name, :string) has_many(:users, Schema.User) end end defmodule Schema.List do use Ecto.Schema use Dx.Ecto.Schema, repo: Repo schema "lists" do field(:title, :string) belongs_to(:created_by, Schema.User) belongs_to(:from_template, Schema.ListTemplate) has_many(:tasks, Schema.Task) field(:archived_at, :utc_datetime) field(:hourly_points, :float) timestamps() end end defmodule Schema.ListTemplate do use Ecto.Schema use Dx.Ecto.Schema, repo: Repo schema "list_templates" do field(:title, :string) field(:hourly_points, :float) has_many(:lists, Schema.List, foreign_key: :from_template_id) end end defmodule Schema.Task do use Ecto.Schema use Dx.Ecto.Schema, repo: Repo schema "tasks" do field(:title, :string) field(:desc, :string) belongs_to(:list, Schema.List) belongs_to(:created_by, Schema.User) field(:due_on, :date) field(:completed_at, :utc_datetime) field(:archived_at, :utc_datetime) timestamps() end end diagram = [Schema.User, Schema.Role, Schema.List, Schema.ListTemplate, Schema.Task] |> Ecto.ERD.Document.render(".mmd", &Function.identity/1, []) Kino.Markdown.new(""" ```mermaid #{diagram} ``` """) ```` ## Dx basics Within defd functions, you can write Elixir code as if all associations are already (pre)loaded: ```elixir defmodule Core.Users do import Dx.Defd defd get_author_names(tasks) do Enum.map(tasks, & &1.created_by.last_name) end end require Dx.Defd tasks = Repo.all(Schema.Task) Dx.Defd.load!(Core.Users.get_author_names(tasks)) ``` defd functions can call other defd functions, so you can structure your code into functions and modules as usual: ```elixir defmodule Core.Users2 do import Dx.Defd defd get_author_names(tasks) do Enum.map(tasks, &author_last_name/1) end defd author_last_name(task) do task.created_by.last_name end end Core.Users2.get_author_names(tasks) ``` ## Use case: Authorization You can also pass schema modules to Enum functions to query additional data, which is not in associations. Data is only queried when needed, for example when an if matches. ```elixir defmodule Core.Authorization do import Dx.Defd defd visible_lists(user) do if admin?(user) do Enum.filter(Schema.List, &(&1.title == "Main list")) else user.lists end end defd admin?(user) do user.role.name == "Admin" end defd get_an_admin() do Enum.find(Schema.User, &admin?/1) end defd get_users_visible_lists(users) do Enum.map(users, &{&1.id, visible_lists(&1)}) end end admin = Dx.Defd.load!(Core.Authorization.get_an_admin()) Dx.Defd.load!(Core.Authorization.visible_lists(admin)) ``` This can just as well be run for multiple users. Queries are batched automatically, so it's efficient. ```elixir Repo.all(Schema.User) |> Core.Authorization.get_users_visible_lists() |> Dx.Defd.load!() ``` ## Use case: Detect dormant users This logic is entirely translated to SQL: ```elixir defmodule Core.User.Filters do import Dx.Defd defd dormant_users(min_days_old) do threshold = DateTime.shift(DateTime.utc_now(), day: -min_days_old) Schema.User |> Enum.filter(&(Enum.count(&1.lists) == 0)) |> Enum.filter(&DateTime.before?(&1.verified_at, threshold)) end end require Dx.Defd Dx.Defd.load!(Core.User.Filters.dormant_users(90)) ``` ## Use case: Commands Since defd functions can be run more often than if it was non-defd, it's a good practice to separate data reading/querying from data manipulation/updating. This example thus returns atoms to determine what should happen next, instead of directly performing the actions. This makes it a pure function and easier to reason about and to test. ```elixir defmodule Core.Workflow do import Dx.Defd defd next_action(user) do cond do not verified?(user) -> :send_verification_reminder Enum.count(user.lists) == 0 -> :send_beginner_tutorial not Enum.any?(user.lists, fn list -> Enum.any?(list.tasks, &task_completed?/1) end) -> :send_advanced_tutorial true -> nil end end defd task_completed?(task), do: not is_nil(task.completed_at) defd verified?(user), do: not is_nil(user.verified_at) end ```
See source

Have you already installed Livebook?

If you already installed Livebook, you can configure the default Livebook location where you want to open notebooks.
Livebook up Checking status We can't reach this Livebook (but we saved your preference anyway)
Run notebook

Not yet? Install Livebook in just a minute

Livebook is open source, free, and ready to run anywhere.

Run on your machine

with Livebook Desktop

Run in the cloud

on select platforms

To run on Linux, Docker, embedded devices, or Elixir’s Mix, check our README.

PLATINUM SPONSORS
SPONSORS
Code navigation with go to definition of modules and functions Read More ×