Elixir Ectoのスキーマ定義と合成可能なクエリ - モジュールからデータベース用DSLへ

More than 3 years have passed since last update.

前回の続き。

今回はEcto.Queryableとクエリ合成、Migrationの詳細。

適切な道具があれば、人間の生産性と思考力は大きく飛躍する。Ectoはデータベースを操作することに特化したプログラミング言語のように操作することができる。

他のプログラミング言語では真似できないほどの柔軟性を持ったElixirだからこそできることだ。

データベースを操作するモジュールのEctoでは、データベースを操作するために作られたプログラミング言語のようなエレガントなAPIを通してアプリケーションを構築することができる。


マイグレーション

classでもstructでもなく、スキーマ定義はschemaマクロで構築できたように、マイグレーション定義においても、SQLシンタックスのように、createdropを通して操作できる。


create

mix ecto.gen.migration create_users

とすると、モジュールと空のchange/0関数のあるファイルが生成されるので以下のようにして、テーブル定義をする。

defmodule MyRepo.Migrations.CreateUsers do

use Ecto.Migration

def change do
create table(:users) do
add :username, :string
add :email, :string
add :age, :integer
add :group_id, references(:groups)

timestamps
end
create index(:users, [:group_id])
create index(:users, [:username], unique: true)
end
end

timestamps:inserted_at:updated_atをカラムに追加する。


drop

dropでテーブルやインデックスを削除する。

drop table(:users)

drop index(:users, [:username])


alter

alterはテーブルの定義を変更するときに使う。

removemodirydescriptionが使える。

alter table(:users) do

remove :age
modify :age, :years
add :description, :text
end


exists?

テーブルの存在を確認したいときはこうする。

elixir:

exists? table(:users)


rename

renameでテーブル名を変更することもできる。

rename table(:users), table(:new_users)


Ectoのクエリ

Elixirのクエリはfromマクロを通して構築することができる。そして関数合成のようにクエリを 合成可能 なことが大きな特徴だ。

クエリは以下のようなお馴染みのSQLのような構文で構築していける。

from u in User,

where: u.age > 20,
select: u.name


使用可能なクエリ


  • distinct

  • exclude

  • from

  • group_by

  • having

  • join

  • limit

  • lock

  • offset

  • order_by

  • preload

  • select

  • update

  • where


使用可能なオペレーター



  • ==, !=, <=, >=, <, >


  • and, or, not

  • in


  • like, ilike

  • is_nil


  • count, avg, sum, min, max


  • datetime_add, date_add


like, ilike

from b in Books,

where: ilike(b.title, "Elixir%")


avg, sum, min, max

from b in Books,

select: avg(b.price)


datetime_add, date_add

直近1ヶ月に発売された本のリストを返すクエリはこう書けば良い。

from book in Books,

where book.published_at > datetime_add(^Ecto.DateTime.utc, -1, "month")


Elixirコードの挿入

Elixirのソースコードをクエリに含めるには変数や式の前に^をつける。

from u in User,

where: u.age > ^age and u.height > ^(height * 100)


クエリの合成

クエリ操作は合成することができる。第一引数をQueryableプロトコルを実装した変数を受け取る関数定義をしておいてパイプ演算子で合成するとかっこいい。

defmodule App.Book do

use App.Web, :model

schema "books" do
field :title, :string
field :price, :integer
field :published_at, Ecto.DateTime

timestamps
end

def published_recently(query) do
from b in query,
where: b.published_at > datetime_add(^Ecto.DateTime.utc, -1, "month")
end

def starts_with(query, title) do
from b in query,
where: ilike(b.title, ^(title <> "%"))
end

def lower_price(query, price) do
from b in query,
where: b.price < ^price
end
end

と定義しておけば、直近1ヶ月で、Elixirから始まるタイトルかつ3000円以下の本を以下のように取得してくることができる。

alias App.Book

Book
|> Book.starts_with("Elixir")
|> Book.lower_price(3000)
|> Book.published_recently
|> Repo.all