Ecto

前提: このガイドでは、入門ガイドの内容を理解し、Phoenixアプリケーションを起動していることを前提としています。

今日のほとんどのWebアプリケーションでは、何らかの形でのデータのバリデーションと永続化が必要です。Elixirのエコシステムでは、これを可能にする Ecto があります。データベースをもったWeb機能を構築する前に、Ectoの詳細に焦点を当てて、Web機能を構築するための強固な基盤となるようにします。さあ、始めましょう!

PhoenixはEctoを使用して、以下のデータベースをサポートしています。

新しく生成されたPhoenixプロジェクトには、デフォルトでPostgreSQLアダプターのEctoが含まれています。--database オプションを使用して変更するか、--no-ecto を使用して除外できます。

Ectoは他のデータベースのサポートも提供しており、多くの学習用リソースも用意されています。一般的な情報はEctoのREADMEをご覧ください。

このガイドでは、Ecto統合で新しいアプリケーションを生成し、PostgreSQLを使用することを前提としています。入門ガイドでは、最初のアプリケーションを起動して実行する方法を説明しています。MySQLへの切り替え方法については、MySQLの使用の項を参照してください。

スキーマとマイグレーションジェネレーターを使用する

EctoとPostgreSQLのインストールと設定が完了したら、Ectoを使用するもっとも簡単な方法は phx.gen.schema タスクでEctoのスキーマを生成することです。Ectoスキーマは、Elixirのデータ型がデータベーステーブルなどの外部ソースとどのようにマッピングされるかを指定するための方法です。ここでは、name, email, bio, number_of_pets フィールドを持つ User スキーマを生成してみましょう。

$ mix phx.gen.schema User users name:string email:string \
bio:string number_of_pets:integer

* creating ./lib/hello/user.ex
* creating priv/repo/migrations/20170523151118_create_users.exs

Remember to update your repository by running migrations:

   $ mix ecto.migrate

このタスクではいくつかのファイルが生成されました。まず、user.ex ファイルがあり、タスクに渡したフィールドのスキーマ定義を含むEctoスキーマが含まれています。次に、priv/repo/migrations/ の中にマイグレーションファイルが生成され、スキーマがマップされるデータベーステーブルが作成されます。

ファイルを用意したので、指示にしたがってマイグレーションを実行してみましょう。

$ mix ecto.migrate
Compiling 1 file (.ex)
Generated hello app

[info]  == Running Hello.Repo.Migrations.CreateUsers.change/0 forward

[info]  create table users

[info]  == Migrated in 0.0s

Mixは、MIX_ENV=prod mix ecto.migrate で指定しない限り、development環境であるとみなします。

データベースサーバーにログインして hello_dev データベースに接続すると、users テーブルが表示されるはずです。Ectoは主キーとして id という整数型のカラムが必要だと想定しているので、そのために生成されたシーケンスが表示されるはずです。

$ psql -U postgres

Type "help" for help.

postgres=# \connect hello_dev
You are now connected to database "hello_dev" as user "postgres".
hello_dev=# \d
                List of relations
 Schema |       Name        |   Type   |  Owner
--------+-------------------+----------+----------
 public | schema_migrations | table    | postgres
 public | users             | table    | postgres
 public | users_id_seq      | sequence | postgres
(3 rows)
hello_dev=# \q

priv/repo/migrations/ にある phx.gen.schema が生成したマイグレーションを見てみると、指定したカラムが追加されていることがわかります。また、timestamps/1 関数を呼び出すことで inserted_atupdated_at のタイムスタンプカラムも追加されます。

defmodule Hello.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :email, :string
      add :bio, :string
      add :number_of_pets, :integer

      timestamps()
    end
  end
end

そして、実際の users テーブルでは次のように変換されます。

$ psql
hello_dev=# \d users
Table "public.users"
Column         |            Type             | Modifiers
---------------+-----------------------------+----------------------------------------------------
id             | bigint                      | not null default nextval('users_id_seq'::regclass)
name           | character varying(255)      |
email          | character varying(255)      |
bio            | character varying(255)      |
number_of_pets | integer                     |
inserted_at    | timestamp without time zone | not null
updated_at     | timestamp without time zone | not null
Indexes:
"users_pkey" PRIMARY KEY, btree (id)

マイグレーションではフィールドとしてリストアップされていませんが、デフォルトでは id カラムが主キーとして取得されていることに注意してください。

Repoの設定

Hello.Repo モジュールは、Phoenixアプリケーションでデータベースを扱うために必要な基盤です。Phoenixはこれを lib/hello/repo.ex で生成してくれました。次に抜粋します。

defmodule Hello.Repo do
  use Ecto.Repo,
    otp_app: :hello,
    adapter: Ecto.Adapters.Postgres
end

まず、リポジトリモジュールを定義します。次に、otp_app という名前と、adapter(ここでは Postgres)を設定します。

私たちのRepoには3つの主要なタスクがあります。それは Ecto.Repo から共通のクエリ関数をすべて取り込むこと、otp_app の名前をアプリケーション名と同じに設定すること、そしてデータベースアダプターを設定することです。Repoの使い方については、もう少し詳しく説明します。

それは、Ecto.Repoから共通のクエリ関数をすべて持ってくること、otp_app にアプリケーション名を設定すること、そして、データベースアダプターを設定することです。Hello.Repo の使い方については、もう少し詳しく説明します。

phx.new でアプリケーションを生成したときには、基本的なリポジトリの設定も含まれていました。config/dev.exs を見てみましょう。

...
# Configure your database
config :hello, Hello.Repo,
  username: "postgres",
  password: "postgres",
  database: "hello_dev",
  hostname: "localhost",
  pool_size: 10
...

また、config/test.exsconfig/runtime.exs(旧 config/prod.secret.exs)にも同様の設定があり、実際の認証情報に合わせて変更できます。

スキーマ

Ectoスキーマは、Elixirの値を外部のデータソースにマッピングしたり、外部のデータをElixirのデータ構造に戻したりする役割を果たします。また、アプリケーション内の他のスキーマとの関係を定義することもできます。たとえば、User スキーマは多くの投稿を持ち、それぞれの投稿はユーザーに属しています。Ectoはまた、データのバリデーションやチェンジセットを使ったタイプキャスティングも処理しますが、これについてはあとで説明します。

これはPhoenixが生成してくれた User スキーマです。

defmodule Hello.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias Hello.User

  schema "users" do
    field :bio, :string
    field :email, :string
    field :name, :string
    field :number_of_pets, :integer

    timestamps()
  end

  @doc false
  def changeset(%User{} = user, attrs) do
    user
    |> cast(attrs, [:name, :email, :bio, :number_of_pets])
    |> validate_required([:name, :email, :bio, :number_of_pets])
  end
end

Ectoスキーマのコアとなるのは、単純にElixirの構造体です。私たちの schema ブロックは、外部の users テーブルとの間で %User{} 構造体フィールドをどのようにキャストするかをEctoに伝えるものです。多くの場合、データベースとの間で単にデータをキャストするだけでは十分ではなく、追加のデータバリデーションが必要になります。そこでEctoチェンジセットの出番です。さあ、飛び込んでみましょう。

チェンジセットとバリデーション

チェンジセットは、アプリケーションで使用する前に必要なデータ変換のパイプラインを定義します。これらの変換には、型キャスト、ユーザー入力のバリデーション、余計なパラメーターのフィルタリングなどが含まれます。多くの場合、データベースへ書き込む前にユーザー入力をバリデーションするためにチェンジセットを使用します。Ectoリポジトリもチェンジセットに対応していて、これは無効なデータを拒否するだけでなく、どのフィールドが変更されたかを知るためにチェンジセットを検査することで、可能な限り最小限のデータベース更新を実行することを可能にします。

デフォルトのチェンジセット関数を詳しく見てみましょう。

def changeset(%User{} = user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_required([:name, :email, :bio, :number_of_pets])
end

今、パイプラインには2つの変換があります。最初の呼び出しでは、Ecto.Changeset.cast/3 を呼び出し、外部パラメーターを渡し、バリデーションに必要なフィールドをマークします。

cast/3 は最初に構造体を受け取り、次にパラメーター(提案されている更新)を受け取り、最後のフィールドは更新されるカラムのリストです。また、cast/3 はスキーマに存在するフィールドのみを取ります。

次に Ecto.Changeset.validate_required/3 は、cast/3 が返すチェンジセットにこのフィールドのリストが存在するかどうかをチェックします。ジェネレーターのデフォルトでは、すべてのフィールドが必須となっています。

この機能は IEx で検証できます。それでは、iex -S mix を実行して IEx 内でアプリケーションを起動してみましょう。タイプを最小限にして読みやすくするために、Hello.User 構造体をエイリアスにしてみましょう。

$ iex -S mix

iex> alias Hello.User
Hello.User

次に、空の User 構造体と空のパラメーターマップを使ってスキーマからチェンジセットを構築してみましょう。

iex> changeset = User.changeset(%User{}, %{})
#Ecto.Changeset<
  action: nil,
  changes: %{},
  errors: [
    name: {"can't be blank", [validation: :required]},
    email: {"can't be blank", [validation: :required]},
    bio: {"can't be blank", [validation: :required]},
    number_of_pets: {"can't be blank", [validation: :required]}
  ],
  data: #Hello.User<>,
  valid?: false
>

チェンジセットがあれば、それが有効かどうかをチェックできます。

iex> changeset.valid?
false

このチェンジセットは有効ではないので、エラーが何であるかを確認できます。

iex> changeset.errors
[
  name: {"can't be blank", [validation: :required]},
  email: {"can't be blank", [validation: :required]},
  bio: {"can't be blank", [validation: :required]},
  number_of_pets: {"can't be blank", [validation: :required]}
]

では、number_of_pets を任意項目にしてみましょう。これを行うには、単純に Hello.Userchangeset/2 関数の中で、リストから削除します。

    |> validate_required([:name, :email, :bio])

さて、チェンジセットをキャストすると、name, email, bio だけが空白にできないことがわかるはずです。これをテストするには、IExの中で recompile() を実行し、チェンジセットを再構築します。

iex> recompile()
Compiling 1 file (.ex)
:ok

iex> changeset = User.changeset(%User{}, %{})
#Ecto.Changeset<
  action: nil,
  changes: %{},
  errors: [
    name: {"can't be blank", [validation: :required]},
    email: {"can't be blank", [validation: :required]},
    bio: {"can't be blank", [validation: :required]}
  ],
  data: #Hello.User<>,
  valid?: false
>

iex> changeset.errors
[
  name: {"can't be blank", [validation: :required]},
  email: {"can't be blank", [validation: :required]},
  bio: {"can't be blank", [validation: :required]}
]

スキーマで定義されていない、または必須ではないキーと値のペアを渡すとどうなるでしょうか?

起動中のIExシェル内で、有効な値に加えて random_key: "random value" を含む params マップを作成してみましょう。

iex> params = %{name: "Joe Example", email: "joe@example.com", bio: "An example to all", number_of_pets: 5, random_key: "random value"}
%{
  bio: "An example to all",
  email: "joe@example.com",
  name: "Joe Example",
  number_of_pets: 5,
  random_key: "random value"
}

次に、新しい params マップを使って別のチェンジセットを作成してみましょう。

iex> changeset = User.changeset(%User{}, params)
#Ecto.Changeset<
  action: nil,
  changes: %{
    bio: "An example to all",
    email: "joe@example.com",
    name: "Joe Example",
    number_of_pets: 5
  },
  errors: [],
  data: #Hello.User<>,
  valid?: true
>

新しいチェンジセットは有効です。

iex> changeset.valid?
true

また、チェンジセットの変更点、つまりすべての変換が完了した後に得られるマップをチェックすることもできます。

iex(9)> changeset.changes
%{bio: "An example to all", email: "joe@example.com", name: "Joe Example",
  number_of_pets: 5}

最終的なチェンジセットから random_key"random_value" が削除されていることに注目してください。チェンジセットを使うと、Webフォームのユーザー入力やCSVファイルからのデータなどの外部データを有効なデータとしてシステムにキャストできます。無効なパラメーターは削除され、スキーマにしたがってキャストできない不正なデータはチェンジセットエラーで強調されます。

バリデーション可能なことはフィールドが必須かどうかだけではありません。より詳細なバリデーションを見てみましょう。

システム内のすべての自己紹介は少なくとも2文字以上の長さでなければならないという要件があったとしたらどうでしょうか?これは、チェンジセットのパイプラインに別の変換を追加して、bio フィールドの長さをバリデーションすることで簡単に行うことができます。

def changeset(%User{} = user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_required([:name, :email, :bio, :number_of_pets])
  |> validate_length(:bio, min: 2)
end

さて、ユーザーの bio"A" の値を含むデータをキャストしようとすると、チェンジセットのエラーにバリデーションの失敗が表示されるはずです。

iex> recompile()

iex> changeset = User.changeset(%User{}, %{bio: "A"})

iex> changeset.errors[:bio]
{"should be at least %{count} character(s)",
 [count: 2, validation: :length, kind: :min, type: :string]}

bioが保持できる最大の長さの要件もあれば、別のバリデーションを追加すればいいだけです。

def changeset(%User{} = user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_required([:name, :email, :bio, :number_of_pets])
  |> validate_length(:bio, min: 2)
  |> validate_length(:bio, max: 140)
end

ここでは、email フィールドに対して、少なくともいくつかの初歩的なフォーマットのバリデーションを行いたいとします。チェックしたいのは"@"の存在だけです。関数 Ecto.Changeset.validate_format/3 はまさにぴったりです。

def changeset(%User{} = user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_required([:name, :email, :bio, :number_of_pets])
  |> validate_length(:bio, min: 2)
  |> validate_length(:bio, max: 140)
  |> validate_format(:email, ~r/@/)
end

"example.com" というメールアドレスでユーザーをキャストしようとすると、以下のようなエラーメッセージが表示されるはずです。

iex> recompile()

iex> changeset = User.changeset(%User{}, %{email: "example.com"})

iex> changeset.errors[:email]
{"has invalid format", [validation: :format]}

チェンジセットで実行できるバリデーションや変換は他にもたくさんあります。詳細は Ecto Changesetのドキュメント を参照してください。

データ永続化

マイグレーションとスキーマを検討しましたが、スキーマやチェンジセットはまだ永続化していません。以前に lib/hello/repo.ex にあるリポジトリモジュールを簡単に見てみましたが、それを使うときがきました。

Ectoリポジトリは、PostgreSQLのようなデータベースであっても、RESTful APIのような外部サービスであっても、ストレージシステムへのインターフェイズです。Repo モジュールの目的は、永続化とデータの問い合わせの詳細を処理することです。呼び出し側としては、データの取得と永続化だけを気にします。Repo は、基礎となるデータベースアダプター通信、コネクションプーリング、データベース制約違反のためのエラー変換を行います。

それでは、iex -S mix を使ってIExに戻り、データベースにユーザーを数人挿入してみましょう。

iex> alias Hello.{Repo, User}
[Hello.Repo, Hello.User]

iex> Repo.insert(%User{email: "user1@example.com"})
[debug] QUERY OK db=6.5ms decode=2.0ms queue=0.5ms idle=1358.3ms
INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user1@example.com", ~N[2021-02-25 01:58:55], ~N[2021-02-25 01:58:55]]
{:ok,
 %Hello.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   bio: nil,
   email: "user1@example.com",
   id: 1,
   inserted_at: ~N[2021-02-25 01:58:55],
   name: nil,
   number_of_pets: nil,
   updated_at: ~N[2021-02-25 01:58:55]
 }}

iex> Repo.insert(%User{email: "user2@example.com"})
[debug] QUERY OK db=1.3ms idle=1402.7ms
INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user2@example.com", ~N[2021-02-25 02:03:28], ~N[2021-02-25 02:03:28]]
{:ok,
 %Hello.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   bio: nil,
   email: "user2@example.com",
   id: 2,
   inserted_at: ~N[2021-02-25 02:03:28],
   name: nil,
   number_of_pets: nil,
   updated_at: ~N[2021-02-25 02:03:28]
 }}

まず、簡単にアクセスできるように、UserRepo のモジュールをエイリアス化しました。次に、Repo.insert/2を呼び出して、ユーザー構造体を渡しました。ここは dev 環境なので、基礎となる %User{} データを挿入するときにリポジトリが実行したクエリのデバッグログを見ることができます。{:ok, %User{}} という2要素のタプルが返されており、挿入が成功したことがわかります。ユーザーが数名挿入されたので、Repoからユーザーを取り出してみましょう。

iex> Repo.all(User)
[debug] QUERY OK source="users" db=5.8ms queue=1.4ms idle=1672.0ms
SELECT u0."id", u0."bio", u0."email", u0."name", u0."number_of_pets", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
[
  %Hello.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    bio: nil,
    email: "user1@example.com",
    id: 1,
    inserted_at: ~N[2021-02-25 01:58:55],
    name: nil,
    number_of_pets: nil,
    updated_at: ~N[2021-02-25 01:58:55]
  },
  %Hello.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    bio: nil,
    email: "user2@example.com",
    id: 2,
    inserted_at: ~N[2021-02-25 02:03:28],
    name: nil,
    number_of_pets: nil,
    updated_at: ~N[2021-02-25 02:03:28]
  }
]

簡単でしたね! [Repo.all/1] はデータソース、この場合は User スキーマを受け取り、それをデータベースに対する基礎となるSQLクエリーに変換します。データを取得した後、RepoはEctoスキーマを使用してデータベースの値を User スキーマにしたがってElixirのデータ構造にマッピングします。Ectoには、基本的なクエリーだけではなく、高度なSQL生成のための本格的なクエリーDSLが含まれています。自然なElixir DSLに加えて、Ectoのクエリーエンジンは、SQLインジェクションの保護やクエリーのコンパイル時の最適化など、複数の優れた機能を提供してくれます。早速試してみましょう。

iex> import Ecto.Query
Ecto.Query

iex> Repo.all(from u in User, select: u.email)
[debug] QUERY OK source="users" db=0.8ms queue=0.9ms idle=1634.0ms
SELECT u0."email" FROM "users" AS u0 []
["user1@example.com", "user2@example.com"]

まず、EctoのクエリーDSLの from/2 マクロをインポートするように Ecto.Query をインポートしました。次に、ユーザーテーブルにあるすべてのメールアドレスを選択するクエリーを作成しました。別の例を試してみましょう。

iex)> Repo.one(from u in User, where: ilike(u.email, "%1%"),
                               select: count(u.id))
[debug] QUERY OK source="users" db=1.6ms SELECT count(u0."id") FROM "users" AS u0 WHERE (u0."email" ILIKE '%1%') []
1

これで、Ectoのリッチなクエリー機能の力を感じられるようになってきました。私たちは Repo.one/2 を使って、1 を含むメールアドレスを持つすべてのユーザーのカウントを取得し、期待されるカウントを返してもらいました。これはEctoのクエリーインターフェイスの表面を掻い摘んだだけで、サブクエリー、インターバルクエリー、高度なセレクト文など、より多くの機能がサポートされています。たとえば、すべてのユーザーIDとそのメールアドレスのマップを取得するクエリーを作成してみましょう。

iex> Repo.all(from u in User, select: %{u.id => u.email})
[debug] QUERY OK source="users" db=0.9ms
SELECT u0."id", u0."email" FROM "users" AS u0 []
[%{3 => "user1@example.com"}, %{4 => "user2@example.com"}]

この小さなクエリーは大きなパンチを持っていました。これは、データベースからすべてのユーザーのメールアドレスをフェッチし、結果のマップを一度に効率的に作成できます。サポートされているクエリー機能の幅の広さを見るには、Ecto.Queryのドキュメントを参照してください。

挿入に加えて、Repo.update/2Repo.delete/2 関数を使って更新や削除を行うこともできます。Ectoはまた、Repo.insert_all/3, Repo.update_all/3, Repo.delete_all/2 関数を使った一括永続化もサポートしています。

Ectoでできることはまだまだたくさんあります。ここまではほんの触りをかじったにすぎません。しっかりとしたEctoの基礎ができたので、アプリの構築を続け、Webアプリケーションとバックエンドの永続化を統合する準備が整いました。途中で、Ectoの知識を広げ、システムの基礎となる詳細からWebインターフェイスを適切に分離する方法を学びます。続きはEctoのドキュメントをご覧ください。

コンテキストガイドでは、関連する機能をグループ化したモジュールの背後にあるEctoのアクセスとビジネスロジックをどのようにまとめるかを見ていきます。Phoenixがメンテナンス性の高いアプリケーションの設計にどのように役立っているかを見ていきます。道に沿って他のきちんとしたEctoの機能を見ていくことでしょう。

MySQLの使用

PhoenixアプリケーションはデフォルトでPostgreSQLを使用するように設定されていますが、代わりにMySQLを使用したい場合はどうすればよいでしょうか?このガイドでは、新しいアプリケーションを作成しようとしている場合でも、既存のアプリケーションがPostgreSQL用に設定されている場合でも、デフォルトを変更する方法を説明します。

新しいアプリケーションを作成しようとしている場合、MySQLを使用するようにアプリケーションを設定するのは簡単です。単に --database mysql フラグを phx.new に渡すだけで、すべてが正しく設定されます。

$ mix phx.new hello_phoenix --database mysql

これにより、正しい依存関係と設定が自動的にセットアップされます。これらの依存関係を mix deps.get でインストールすると、アプリケーションでEctoを使い始める準備が整います。

既存のアプリケーションがあれば、アダプターを切り替えてちょっとした設定変更をするだけです。

アダプターを切り替えるには、Postgrexの依存関係を削除し、代わりにMyxql用の新しいものを追加する必要があります。

それでは、mix.exs ファイルを開いて、切り替えてみましょう。

defmodule HelloPhoenix.MixProject do
  use Mix.Project

  . . .
  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:phoenix, "~> 1.4.0"},
      {:phoenix_ecto, "~> 4.0"},
      {:ecto_sql, "~> 3.6"},
      {:myxql, ">= 0.0.0"},
      ...
    ]
  end
end

次に、デフォルトのMySQL認証情報を使用するようにアダプターを設定する必要があります。config/dev.exs ファイルを開いて設定してみましょう。

config :hello_phoenix, HelloPhoenix.Repo,
username: "root",
password: "",
database: "hello_phoenix_dev"

もし、HelloPhoenix.Repo 用の既存の設定ブロックがあれば、その値を新しいものに合わせて変更するだけです。また、config/test.exsconfig/runtime.exs(旧 config/prod.secret.exs)ファイルにも正しい値を設定する必要があります。

最後の変更点は、lib/hello_phoenix/repo.ex を開き、:adapterEcto.Adapterers.MyXQL に設定することです。

あとは新しい依存関係を取得するだけです。

$ mix do deps.get, compile

新しいアダプターがインストールされ、設定されたので、データベースを作成する準備が整いました。

$ mix ecto.create

HelloPhoenix.Repoのデータベースが作成されました。
Ectoを使用して、マイグレーションやその他の作業を行う準備もできています。

$ mix ecto.migrate
[info] == Running HelloPhoenix.Repo.Migrations.CreateUser.change/0 forward
[info] create table users
[info] == Migrated in 0.2s

その他のオプション

Phoenixは Ecto プロジェクトを使ってデータアクセスレイヤーと対話していますが、他にも多くのデータアクセスオプションがあり、Erlang標準ライブラリに組み込まれているものもあります。ETSDETSは、OTPに組み込まれたキーバリューデータストアです。OTPはまた、mnesiaというリレーショナルデータベースをQLCと呼ばれる独自のクエリ言語と共に提供しています。ElixirとErlangの両方には、幅広い一般的なデータストアを扱うための多くのライブラリもあります。

Phoenixはデータアクセス層とのやりとりに Ecto プロジェクトを使っていますが、他にも多くのデータアクセスオプションがあり、中にはErlangの標準ライブラリに組み込まれているものもあります。ETS - Ectoではetso経由で利用できます - DETSOTPに組み込まれたキーバリューデータストアです。OTPはまた、MnesiaというリレーショナルデータベースをQLCという独自のクエリ言語とともに提供しています。ElixirとErlangには、人気のあるさまざまなデータストアを扱うためのライブラリがあります。

データの世界はあなたの思いのままですが、このガイドではこれらのオプションは取り上げません。