(追記)本記事は2022/09/25 に更新しました
これまでElixir Ectoについて2本の記事を書いてきましたが、クイックスタート的な記事も書いておきたいと思いました。言葉だけで説明するよりコマンドを打ち込んで説明していきたいと思います。少し離れていると基本的なところを忘れがちなので、自分用の備忘録的な意味合いも強いです。例としてよく使われるであろうコマンドを書きましたが、網羅的な説明はやはり公式サイトを参照するのが良いでしょう。
Ecto.Repo - 公式サイト
Ecto.Schema - 公式サイト
Ecto.Changeset - 公式サイト
Ecto.Query - 公式サイト
#0.Ectoの設定
まずはEctoの設定を、実際にプロジェクトを作成しながら行っていきます。mixでpeopleというプロジェクトを作成します。オプションの --sup はこのアプリケーションがsupervision treeを持っていることを示しています。
mix new people --sup
次に必要なパッケージをインストールします。以下のファイルのdepsを変更します。postgrexはPostgreSQLのドライバです。
defp deps do
[
{:ecto_sql, "~> 3.2"},
{:postgrex, "~> 0.15"}
]
end
インストールします。
mix deps.get
まず以下のコマンドでRepo(lib/ecto_assoc/repo.ex)を作成し、config/config.exsにDB設定を追加します。
mix ecto.gen.repo -r People.Repo
設定ファイルを確認し、username/password などを自分の環境に合わせます。
config :people, People.Repo,
database: "people_repo",
username: "postgres",
password: "postgres",
hostname: "localhost"
作成されたRepoの確認だけを行います。People.Repo はデータベースへのqueryに使われます。ここではEcto.Repoを使うことが宣言されています。otp_appはどのアプリがデータベースへアクセスしているかを教えています。:people となっているのでこのプロジェクトのconfig.exsを参照します。その接続情報をもとにデータベースにアクセスします。
defmodule People.Repo do
use Ecto.Repo,
otp_app: :people,
adapter: Ecto.Adapters.Postgres
end
次にlib/people/application.exを編集します。People.Repo をapplication の supervision treeの中のsupervisor としてセットします。これによって、Ecto process を起動して、アプリのqueryを受け取り実行できるようになります。
#
def start(_type, _args) do
import Supervisor.Spec
children = [
People.Repo
]
#
最後にconfig/config.exsを編集して、以下の一行を追加します。これでアプリケーションにrepoについて教えます。
config :people, ecto_repos: [People.Repo]
以下のコマンドでデーターベースを作成します。
mix ecto.create
(備忘録) psqlコマンドでDatabaseを確認する
sudo -u postgres -i
次にテーブルの作成に移ります。
mix ecto.gen.migration create_person
作成されたmigrationsを以下のように修正します。
defmodule People.Repo.Migrations.CreatePerson do
use Ecto.Migration
def change do
create table(:people) do
add :first_name, :string
add :last_name, :string
add :age, :integer
end
end
end
Person schemaを作成します
defmodule People.Person do
use Ecto.Schema
schema "people" do
field :first_name, :string
field :last_name, :string
field :age, :integer
end
def changeset(person, params \\ %{}) do
person
|> Ecto.Changeset.cast(params, [:first_name, :last_name, :age])
|> Ecto.Changeset.validate_required([:first_name, :last_name])
end
end
以上で準備が整いましたので、実際にテーブルを作成したいと思います。
mix ecto.migrate
#1.Schema structでのDB操作
schema structを使ってDBを操作してみます。
iex -S mix
### aliasを切ってRepoとPersonを直接アクセスできるようにします
iex(1)> alias People.{Repo, Person}
[People.Repo, People.Person]
### Person structを作成
iex(2)> person= %Person{}
%People.Person{
__meta__: #Ecto.Schema.Metadata<:built, "people">,
age: nil,
first_name: nil,
id: nil,
last_name: nil
}
### Person structをテーブルに挿入
iex(3)> person = Repo.insert(person)
{:ok,
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: nil,
first_name: nil,
id: 1,
last_name: nil
}}
### id=1 の Personを取得
iex(4)> person = Repo.get(Person, 1)
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: nil,
first_name: nil,
id: 1,
last_name: nil
}
### 全てのPersonを取得
iex(5)> Repo.all(Person)
[
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: nil,
first_name: nil,
id: 1,
last_name: nil
}
]
### 指定したPerson structをテーブルから削除
iex(6)> Repo.delete(person)
{:ok,
%People.Person{
__meta__: #Ecto.Schema.Metadata<:deleted, "people">,
age: nil,
first_name: nil,
id: 1,
last_name: nil
}}
### 削除後に全てのPersonを取得 ==>何も表示されない
iex(7)> Repo.all(Person)
#2.ChangesetでのDB操作
前の章では、Ecto.Repo.insertを使って、Schema structをDBに挿入しました。混乱しやすいのですが、Ecto.Repo.insertを使って、Changesetを挿入することもできます。公式ドキュメントには以下のように記述されています。
Ecto.Repo.insert(struct_or_changeset, opts)
Inserts a struct defined via Ecto.Schema or a changeset
本章ではchangesetを使ったDB操作を見たいと思います。
### DBをクリアーします
mix ecto.drop
mix ecto.create
mix ecto.migrate
iex -S mix
iex(1)> alias People.{Repo, Person}
[People.Repo, People.Person]
### Scema struct(person)を作成
iex(2)> person= %Person{}
%People.Person{
__meta__: #Ecto.Schema.Metadata<:built, "people">,
age: nil,
first_name: nil,
id: nil,
last_name: nil
}
### Scema struct(person)からchangesetを作成
iex(3)> changeset =Person.changeset(person, %{first_name: "Ryan", last_name: "Bigg"})
#Ecto.Changeset<
action: nil,
changes: %{first_name: "Ryan", last_name: "Bigg"},
errors: [],
data: #People.Person<>,
valid?: true
>
### changesetをテーブルに挿入
iex(4)> Repo.insert(changeset)
{:ok,
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: nil,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}}
### id=1 の Personを取得
iex(5)> person = Repo.get(Person,1)
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: nil,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}
### Scema struct(person)からchangesetを作成
iex(6)> changeset = Person.changeset(person, %{age: 29})
#Ecto.Changeset<
action: nil,
changes: %{age: 29},
errors: [],
data: #People.Person<>,
valid?: true
>
### changesetでテーブルを更新
iex(7)> person=Repo.update(changeset)
{:ok,
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}}
### 一応、personを再確認
iex(8)> person
{:ok,
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}}
#3.Ecto.Query
### DBをクリアーします
mix ecto.drop
mix ecto.create
mix ecto.migrate
iex -S mix
iex(1)> alias People.{Repo, Person}
[People.Repo, People.Person]
### 4つのPerson structを作成し、テーブルに挿入
iex(2)> people = [
...(2)> %Person{first_name: "Ryan", last_name: "Bigg", age: 28},
...(2)> %Person{first_name: "John", last_name: "Smith", age: 27},
...(2)> %Person{first_name: "Jane", last_name: "Smith", age: 26},
...(2)> %Person{first_name: "Taro", last_name: "Yamada", age: 29},
...(2)> ]
iex(3)> Enum.each(people, fn (person) -> Repo.insert(person) end)
### Person structの最後のものを取得
iex(4)> Person |> Ecto.Query.last |> Repo.one
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Taro",
id: 4,
last_name: "Yamada"
}
### Person structの最初のものを取得
iex(5)> Person |> Ecto.Query.first |> Repo.one
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}
### queryの構築と実行(1)
iex(6)> import Ecto.Query, only: [from: 2]
Ecto.Query
iex(7)> query = from u in "people", where: u.age > 27, select: u.first_name
#Ecto.Query<from p in "people", where: p.age > 27, select: p.first_name>
iex(8)> Repo.all(query)
["Ryan", "Taro"]
### queryの構築と実行(2)
iex(9)> age="26"
iex(10)> Repo.all(from u in Person, where: u.age > ^age)
[
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Taro",
id: 4,
last_name: "Yamada"
}
]
### queryの構築と実行(3) --- order_by
iex(11)> import Ecto.Query, only: [order_by: 2]
Ecto.Query
iex(12)> Person |> order_by(asc: :first_name)|>Repo.all
[
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Taro",
id: 4,
last_name: "Yamada"
}
]
iex(13)> Person |> order_by(desc: :first_name)|>Repo.all
[
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Taro",
id: 4,
last_name: "Yamada"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
}
]
### Repo.all(Person) と Repo.all(from p in Person) は同じ結果
iex(2)> Repo.all(Person)
SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 []
[
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Taro",
id: 4,
last_name: "Yamada"
}
]
iex(4)> import Ecto.Query, only: [from: 1]
Ecto.Query
iex(5)> Repo.all(from p in Person)
SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 []
[
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
},
%People.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Taro",
id: 4,
last_name: "Yamada"
}
]
以上です。
■ Elixir/Phoenixの基礎についてまとめた過去記事
Elixir Ecto チュートリアル - Qiita
Elixir Ecto のまとめ - Qiita
Elixir Ecto Association - Qiita
Phoenix1.6の基本的な仕組み - Qiita
Phoenixのログイン管理のためのSessionの使い方 - Qiita
Phoenix1.3のUserアカウントとSession - Qiita
Phoenix1.3+Guardian1.0でJWT - Qiita