Elixir Ecto チュートリアル

**(追記)本記事は2022/09/25 に更新しました**

　これまでElixir Ectoについて２本の記事を書いてきましたが、クイックスタート的な記事も書いておきたいと思いました。言葉だけで説明するよりコマンドを打ち込んで説明していきたいと思います。少し離れていると基本的なところを忘れがちなので、自分用の備忘録的な意味合いも強いです。例としてよく使われるであろうコマンドを書きましたが、網羅的な説明はやはり公式サイトを参照するのが良いでしょう。


[Ecto.Repo - 公式サイト](https://hexdocs.pm/ecto/Ecto.Repo.html)
[Ecto.Schema - 公式サイト](https://hexdocs.pm/ecto/Ecto.Schema.html)
[Ecto.Changeset - 公式サイト](https://hexdocs.pm/ecto/Ecto.Changeset.html)
[Ecto.Query - 公式サイト](https://hexdocs.pm/ecto/Ecto.Query.html)


#０．Ectoの設定

　まずはEctoの設定を、実際にプロジェクトを作成しながら行っていきます。mixでpeopleというプロジェクトを作成します。オプションの --sup はこのアプリケーションがsupervision treeを持っていることを示しています。

```sh:
mix new people --sup
```

　次に必要なパッケージをインストールします。以下のファイルのdepsを変更します。postgrexはPostgreSQLのドライバです。

```elixir:mix.exs
defp deps do
    [
      {:ecto_sql, "~> 3.2"},
      {:postgrex, "~> 0.15"}
    ]
end
```


　インストールします。

```sh:
mix deps.get
```

　まず以下のコマンドでRepo（lib/ecto_assoc/repo.ex）を作成し、config/config.exsにDB設定を追加します。


```sh:
mix ecto.gen.repo -r People.Repo
```

　設定ファイルを確認し、username/password などを自分の環境に合わせます。

```elixir:config/config.exs
config :people, People.Repo,
  database: "people_repo",
  username: "postgres",
  password: "postgres",
  hostname: "localhost"
```

　作成されたRepoの確認だけを行います。People.Repo はデータベースへのqueryに使われます。ここではEcto.Repoを使うことが宣言されています。otp_appはどのアプリがデータベースへアクセスしているかを教えています。:people となっているのでこのプロジェクトのconfig.exsを参照します。その接続情報をもとにデータベースにアクセスします。

```elixir:lib/people/repo.ex
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を受け取り実行できるようになります。

```elixir:lib/people/application.ex
#
  def start(_type, _args) do
    import Supervisor.Spec
    children = [
        People.Repo
    ]
#
```

　最後にconfig/config.exsを編集して、以下の一行を追加します。これでアプリケーションにrepoについて教えます。

```elixir:config/config.exs
    config :people, ecto_repos: [People.Repo]
```

以下のコマンドでデーターベースを作成します。

```
mix ecto.create
```

**（備忘録）** psqlコマンドでDatabaseを確認する
**sudo -u postgres -i**


次にテーブルの作成に移ります。

```
mix ecto.gen.migration create_person
```


作成されたmigrationsを以下のように修正します。

```elixir:priv/repo/migrations/20180909080641_create_person.exs
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を作成します

```elixir:lib/people/person.ex
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
```



#１．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)
```



#２．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"
 }}
```



#３．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]


### ４つの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](https://qiita.com/sand/items/0861ad7a16f7ac612f41)
[Elixir Ecto のまとめ - Qiita](https://qiita.com/sand/items/71d0b35d74a4781f3564)
[Elixir Ecto Association - Qiita](https://qiita.com/sand/items/5581497972473e308f05)
[Phoenix1.6の基本的な仕組み - Qiita](https://qiita.com/sand/items/f42dc35c47589eab687e)
[Phoenixのログイン管理のためのSessionの使い方 - Qiita](https://qiita.com/sand/items/a88f1c39f3a6f7a01f99)
[Phoenix1.3のUserアカウントとSession - Qiita](https://qiita.com/sand/items/8e541f8a950ae6c63931)
[Phoenix1.3+Guardian1.0でJWT - Qiita](https://qiita.com/sand/items/393827194e687efafd4f)