前回の続き。
今回はEcto.Queryableとクエリ合成、Migrationの詳細。
適切な道具があれば、人間の生産性と思考力は大きく飛躍する。Ectoはデータベースを操作することに特化したプログラミング言語のように操作することができる。
他のプログラミング言語では真似できないほどの柔軟性を持ったElixirだからこそできることだ。
データベースを操作するモジュールのEctoでは、データベースを操作するために作られたプログラミング言語のようなエレガントなAPIを通してアプリケーションを構築することができる。
マイグレーション
class
でもstruct
でもなく、スキーマ定義はschema
マクロで構築できたように、マイグレーション定義においても、SQLシンタックスのように、create
やdrop
を通して操作できる。
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はテーブルの定義を変更するときに使う。
remove
、modiry
、description
が使える。
alter table(:users) do
remove :age
modify :age, :years
add :description, :text
end
exists?
テーブルの存在を確認したいときはこうする。
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