LoginSignup
6
6

More than 1 year has passed since last update.

Elixir Ecto チュートリアル

Last updated at Posted at 2018-09-09

(追記)本記事は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のドライバです。

mix.exs
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/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を参照します。その接続情報をもとにデータベースにアクセスします。

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を受け取り実行できるようになります。

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

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

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を以下のように修正します。

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を作成します

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

#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

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6