Ecto
と PostgreSQL
でいろいろお試しします。
-
macOS
$ psql --version psql (PostgreSQL) 12.2 $ elixir --version Erlang/OTP 22 [erts-10.5.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] Elixir 1.9.2 (compiled with Erlang/OTP 22)
Hexdocsを元に実行します。
-
bash
$ mix new friends --sup * creating README.md * creating .formatter.exs * creating .gitignore * creating mix.exs * creating lib * creating lib/friends.ex * creating lib/friends/application.ex * creating test * creating test/test_helper.exs * creating test/friends_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd friends mix test Run "mix help" for more commands.
cd friends
-
mix.exs
defp deps do [ {:ecto_sql, "~> 3.0"}, -> add {:postgrex, ">= 0.0.0"} -> add
-
bash
$ mix deps.get Resolving Hex dependencies... Dependency resolution completed: New: connection 1.0.4 db_connection 2.2.2 decimal 1.8.1 ecto 3.4.4 ecto_sql 3.4.3 postgrex 0.15.4 telemetry 0.4.1 * Getting ecto_sql (Hex package) * Getting postgrex (Hex package) * Getting connection (Hex package) * Getting db_connection (Hex package) * Getting decimal (Hex package) * Getting ecto (Hex package) * Getting telemetry (Hex package)
$ mix ecto.gen.repo -r Friends.Repo ==> connection Compiling 1 file (.ex) Generated connection app ===> Compiling telemetry ==> decimal Compiling 1 file (.ex) Generated decimal app ==> db_connection Compiling 14 files (.ex) Generated db_connection app ==> ecto Compiling 55 files (.ex) Generated ecto app ==> postgrex Compiling 61 files (.ex) Generated postgrex app ==> ecto_sql Compiling 26 files (.ex) Generated ecto_sql app ==> friends * creating lib/friends * creating lib/friends/repo.ex * creating config/config.exs Don't forget to add your new repo to your supervision tree (typically in lib/friends/application.ex): {Friends.Repo, []} And to add it to the list of ecto repositories in your configuration files (so Ecto tasks work as expected): config :friends, ecto_repos: [Friends.Repo]
-
config/config.exs
config :friends, Friends.Repo, database: "friends_repo", username: "postgres", password: "postgres", hostname: "localhost" config :friends, ecto_repos: [Friends.Repo] --> add
-
lib/friends/application
def start(_type, _args) do children = [ Friends.Repo, --> add
-
bash
$ mix ecto.create Compiling 3 files (.ex) Generated friends app The database for Friends.Repo has been created
$ psql -l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
----------------------+----------+----------+-------------+-------------+-------------------
friends_repo | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 |
.
.
.
- Ectoでの DBの作成 に成功しました。
-
bash
$ mix ecto.gen.migration create_people * creating priv/repo/migrations * creating priv/repo/migrations/20200517041113_create_people.exs
-
priv/repo/migrations/20200517041113_create_people.exs
def change do create table(:people) do --> add add :first_name, :string --> add add :last_name, :string --> add add :age, :integer --> add end
-
bash
$ mix ecto.migrate 13:28:21.372 [info] == Running 20200517041113 Friends.Repo.Migrations.CreatePeople.change/0 forward 13:28:21.373 [info] create table people 13:28:21.382 [info] == Migrated 20200517041113 in 0.0s
$ psql friends_repo
psql (12.2)
Type "help" for help.
friends_repo=# \dt
List of relations
Schema | Name | Type | Owner
--------+-------------------+-------+----------
public | people | table | postgres
public | schema_migrations | table | postgres
(2 rows)
friends_repo=# \d people
Table "public.people"
Column | Type | Collation | Nullable | Default
------------+------------------------+-----------+----------+------------------------------------
id | bigint | | not null | nextval('people_id_seq'::regclass)
first_name | character varying(255) | | |
last_name | character varying(255) | | |
age | integer | | |
Indexes:
"people_pkey" PRIMARY KEY, btree (id)
-
Ectoでの DBテーブル作成 に成功しました。
-
(メモ)
-
マイグレーションでミスがあった場合、
mix ecto.rollback
で変更を元に戻すことが可能。
(その後、変更修正してから、再度mix ecto.create
を実行する) -
この段階で
mix ecto.rollback
すると、いま作成したばかりのテーブルを削除可能。
-
insert()
-
ファイル作成:
lib/friends/person.ex
defmodule Friends.Person do use Ecto.Schema schema "people" do field :first_name, :string field :last_name, :string field :age, :integer end end
-
$ iex -S mix
で検証iex(1)> person = %Friends.Person{} %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:built, "people">, age: nil, first_name: nil, id: nil, last_name: nil } iex(2)> person = %Friends.Person{age: 28} %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:built, "people">, age: 28, first_name: nil, id: nil, last_name: nil } iex(3)> person.age 28 iex(4)> Friends.Repo.insert(person) 15:42:21.463 [debug] QUERY OK db=2.9ms decode=1.2ms queue=1.1ms idle=27.4ms INSERT INTO "people" ("age") VALUES ($1) RETURNING "id" [28] {:ok, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: nil, id: 1, last_name: nil }}
-
lib/friends/person.ex
のdefmodule
内に、以下を追加def changeset(person, params \\ %{}) do person |> Ecto.Changeset.cast(params, [:first_name, :last_name, :age]) |> Ecto.Changeset.validate_required([:first_name, :last_name]) end
-
$ iex -S mix
で検証 (バリデーションエラー パターン)iex(1)> person = %Friends.Person{} %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:built, "people">, age: nil, first_name: nil, id: nil, last_name: nil } iex(2)> changeset = Friends.Person.changeset(person, %{}) #Ecto.Changeset< action: nil, changes: %{}, errors: [ first_name: {"can't be blank", [validation: :required]}, last_name: {"can't be blank", [validation: :required]} ], data: #Friends.Person<>, valid?: false > iex(3)> Friends.Repo.insert(changeset) {:error, #Ecto.Changeset< action: :insert, changes: %{}, errors: [ first_name: {"can't be blank", [validation: :required]}, last_name: {"can't be blank", [validation: :required]} ], data: #Friends.Person<>, valid?: false >} iex(4)> changeset.valid? false
- ちゃんと
:error
が返り、バリデーションが効いていることを確認できました。
- ちゃんと
-
$ iex -S mix
で検証 (サクセス パターン)データの
INSERT
(CREATE処理) まで合わせて実行します。iex(5)> person = %Friends.Person{} %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:built, "people">, age: nil, first_name: nil, id: nil, last_name: nil } iex(6)> changeset = Friends.Person.changeset(person, %{first_name: "im", last_name: "miolab"}) #Ecto.Changeset< action: nil, changes: %{first_name: "im", last_name: "miolab"}, errors: [], data: #Friends.Person<>, valid?: true > iex(7)> changeset.errors [] iex(8)> changeset.valid? true iex(9)> Friends.Repo.insert(changeset) 16:15:47.461 [debug] QUERY OK db=1.7ms decode=1.1ms queue=1.4ms idle=421.1ms INSERT INTO "people" ("first_name","last_name") VALUES ($1,$2) RETURNING "id" ["im", "miolab"] {:ok, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: nil, first_name: "im", id: 2, last_name: "miolab" }}
$ psql friends_repo
psql (12.2)
Type "help" for help.
friends_repo=# select * from people;
id | first_name | last_name | age
----+------------+-----------+-----
1 | | | 28
2 | im | miolab |
(2 rows)
-
Ectoでの CREATE 処理に成功しました。
-
id: 2
レコードで、first_name
last_name
のバリデーションも効いています。
-
事前準備として、DBを再作成します。
-
PostgreSQL
# exit
-
bash
$ mix ecto.drop The database for Friends.Repo has been dropped $ mix ecto.create The database for Friends.Repo has been created $ mix ecto.migrate 08:14:06.052 [info] == Running 20200517041113 Friends.Repo.Migrations.CreatePeople.change/0 forward 08:14:06.055 [info] create table people 08:14:06.078 [info] == Migrated 20200517041113 in 0.0s
-
PostgreSQL
$ psql friends_repo friends_repo=# select * from people; id | first_name | last_name | age ----+------------+-----------+----- (0 rows)
DBが再作成されました。
-
-
$ iex -S mix
で以下を実行。クエリを構築 → クエリをリポジトリに渡す → DBに対してクエリを実行
iex(1)> people = [ ...(1)> %Friends.Person{first_name: "im", last_name: "miolab", age: 28}, ...(1)> %Friends.Person{first_name: "foo", last_name: "miolab", age: 27}, ...(1)> %Friends.Person{first_name: "foobar", last_name: "hogehoge", age: 26}, ...(1)> %Friends.Person{first_name: "eli", last_name: "xir", age: 26}, ...(1)> ] [ %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:built, "people">, age: 28, first_name: "im", id: nil, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:built, "people">, age: 27, first_name: "foo", id: nil, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:built, "people">, age: 26, first_name: "foobar", id: nil, last_name: "hogehoge" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:built, "people">, age: 26, first_name: "eli", id: nil, last_name: "xir" } ] iex(2)> Enum.each(people, fn(person) -> Friends.Repo.insert(person) end) 10:17:19.023 [debug] QUERY OK db=2.3ms decode=1.0ms queue=2.6ms idle=1198.7ms INSERT INTO "people" ("age","first_name","last_name") VALUES ($1,$2,$3) RETURNING "id" [28, "im", "miolab"] 10:17:19.027 [debug] QUERY OK db=1.1ms queue=1.1ms idle=1208.3ms INSERT INTO "people" ("age","first_name","last_name") VALUES ($1,$2,$3) RETURNING "id" [27, "foo", "miolab"] 10:17:19.030 [debug] QUERY OK db=1.0ms queue=0.9ms idle=1210.7ms INSERT INTO "people" ("age","first_name","last_name") VALUES ($1,$2,$3) RETURNING "id" [26, "foobar", "hogehoge"] 10:17:19.032 [debug] QUERY OK db=1.0ms queue=0.9ms idle=1212.8ms INSERT INTO "people" ("age","first_name","last_name") VALUES ($1,$2,$3) RETURNING "id" [26, "eli", "xir"] :ok
(※
changeset
不経由)
friends_repo=# select * from people;
id | first_name | last_name | age
----+------------+-----------+-----
1 | im | miolab | 28
2 | foo | miolab | 27
3 | foobar | hogehoge | 26
4 | eli | xir | 26
(4 rows)
get()
get_by()
where()
$ iex -S mix
で確認。
-
最初のレコード 取得
iex(1)> Friends.Person |> Ecto.Query.first |> Friends.Repo.one 11:17:20.928 [debug] QUERY OK source="people" db=0.8ms decode=1.6ms queue=1.5ms idle=1791.5ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 ORDER BY p0."id" LIMIT 1 [] %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" }
-
最後のレコード 取得
iex(2)> Friends.Person |> Ecto.Query.last |> Friends.Repo.one 11:17:46.505 [debug] QUERY OK source="people" db=1.5ms queue=2.5ms idle=1377.0ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 ORDER BY p0."id" DESC LIMIT 1 [] %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 26, first_name: "eli", id: 4, last_name: "xir" }
-
全レコード 取得
iex(3)> Friends.Person |> Friends.Repo.all 11:19:58.105 [debug] QUERY OK source="people" db=0.3ms queue=0.4ms idle=1981.1ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 [] [ %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27, first_name: "foo", id: 2, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 26, first_name: "foobar", id: 3, last_name: "hogehoge" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 26, first_name: "eli", id: 4, last_name: "xir" } ]
-
id
を指定 してレコード取得iex(4)> Friends.Person |> Friends.Repo.get(1) 11:21:08.927 [debug] QUERY OK source="people" db=1.2ms queue=1.6ms idle=1801.0ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."id" = $1) [1] %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" } iex(5)> Friends.Person |> Friends.Repo.get(2) 11:21:14.877 [debug] QUERY OK source="people" db=3.9ms idle=1749.7ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."id" = $1) [2] %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27, first_name: "foo", id: 2, last_name: "miolab" }
-
カラム属性 に基づいてレコードを取得
iex(6)> Friends.Person |> Friends.Repo.get_by(first_name: "im") 11:23:40.635 [debug] QUERY OK source="people" db=1.4ms queue=5.4ms idle=1504.6ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."first_name" = $1) ["im"] %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" }
-
フィルタリング抽出(
Ecto.Query
併用)iex(7)> require Ecto.Query Ecto.Query iex(8)> Friends.Person |> Ecto.Query.where(last_name: "miolab") |> Friends.Repo.all 11:34:11.847 [debug] QUERY OK source="people" db=0.5ms queue=1.5ms idle=1721.2ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."last_name" = 'miolab') [] [ %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27, first_name: "foo", id: 2, last_name: "miolab" } ]
-
別記法
iex(9)> Ecto.Query.from(p in Friends.Person, where: p.last_name == "miolab") |> Friends.Repo.all 12:02:34.174 [debug] QUERY OK source="people" db=0.9ms queue=0.1ms idle=1563.1ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."last_name" = 'miolab') [] [ %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27, first_name: "foo", id: 2, last_name: "miolab" } ]
-
Note : 変数をクエリ内で展開する場合は、
^
(ピン演算子)が必要。iex(10)> last_name_miolab = "miolab" "miolab" iex(11)> Friends.Person |> Ecto.Query.where(last_name: ^last_name_miolab) |> Friends.Repo.all 11:52:23.130 [debug] QUERY OK source="people" db=1.3ms queue=2.7ms idle=1518.1ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."last_name" = $1) ["miolab"] [ %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27, first_name: "foo", id: 2, last_name: "miolab" } ] iex(12)> Ecto.Query.from(p in Friends.Person, where: p.last_name == ^last_name_miolab) |> Friends.Repo.all 12:03:02.104 [debug] QUERY OK source="people" db=2.0ms idle=1493.0ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."last_name" = $1) ["miolab"] [ %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27, first_name: "foo", id: 2, last_name: "miolab" } ]
-
クエリのアレンジ(使用例)
last_name
抽出に、first_name
抽出を追加してAND
検索となるようクエリをアレンジします。iex(13)> query = Friends.Person |> Ecto.Query.where(last_name: "miolab") #Ecto.Query<from p0 in Friends.Person, where: p0.last_name == "miolab"> iex(14)> query_fullname = query |> Ecto.Query.where(first_name: "im") #Ecto.Query<from p0 in Friends.Person, where: p0.last_name == "miolab", where: p0.first_name == "im"> iex(15)> query_fullname |> Friends.Repo.all 12:20:26.074 [debug] QUERY OK source="people" db=0.2ms idle=1460.8ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."last_name" = 'miolab') AND (p0."first_name" = 'im') [] [ %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" } ]
-
update()
$ iex -S mix
で確認。
-
iex
iex(1)> require Ecto.Query Ecto.Query iex(2)> person = Friends.Person |> Ecto.Query.first |> Friends.Repo.one 13:09:01.291 [debug] QUERY OK source="people" db=0.9ms decode=1.5ms queue=1.3ms idle=1885.6ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 ORDER BY p0."id" LIMIT 1 [] %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 28, first_name: "im", id: 1, last_name: "miolab" } iex(3)> changeset = Friends.Person.changeset(person, %{age: 18}) #Ecto.Changeset< action: nil, changes: %{age: 18}, errors: [], data: #Friends.Person<>, valid?: true > iex(4)> Friends.Repo.update(changeset) 13:10:28.235 [debug] QUERY OK db=1.8ms queue=2.5ms idle=1835.9ms UPDATE "people" SET "age" = $1 WHERE "id" = $2 [18, 1] {:ok, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 18, first_name: "im", id: 1, last_name: "miolab" }}
friends_repo=# select * from people;
id | first_name | last_name | age
----+------------+-----------+-----
2 | foo | miolab | 27
3 | foobar | hogehoge | 26
4 | eli | xir | 26
1 | im | miolab | 18
(4 rows)
delete()
$ iex -S mix
で確認。
-
iex
id: 3
のレコードを削除します。iex(5)> person_delete = Friends.Repo.get(Friends.Person, 3) 13:44:06.427 [debug] QUERY OK source="people" db=0.8ms idle=1014.5ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 WHERE (p0."id" = $1) [3] %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 26, first_name: "foobar", id: 3, last_name: "hogehoge" } iex(6)> Friends.Repo.delete(person_delete) 13:44:47.048 [debug] QUERY OK db=2.4ms queue=2.1ms idle=1632.1ms DELETE FROM "people" WHERE "id" = $1 [3] {:ok, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:deleted, "people">, age: 26, first_name: "foobar", id: 3, last_name: "hogehoge" }}
-
iex
iex(7)> Friends.Person |> Friends.Repo.all 13:45:27.760 [debug] QUERY OK source="people" db=0.5ms queue=0.9ms idle=1347.4ms SELECT p0."id", p0."first_name", p0."last_name", p0."age" FROM "people" AS p0 [] [ %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 27, first_name: "foo", id: 2, last_name: "miolab" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 26, first_name: "eli", id: 4, last_name: "xir" }, %Friends.Person{ __meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: 18, first_name: "im", id: 1, last_name: "miolab" } ]
id: 3
のレコードが削除されていることを確認できました。