Rails使いがElixirのEctoを勉強した時のまとめ

Rubyに影響を受けた(というより弟分)言語であるElixirが熱いです。
RailsインスパイアのElixirのWAFであるPhoenixはrailsの[10倍早いらしい](https://github.com/mroth/phoenix-showdown)です。

何かと話題の関数型言語elixir + phoenixを勉強中にデータベースラッパー&クエリジェネレータであるEctoの使い方がrailsのActiveRecordとは違う雰囲気で色々と悩んだのでメモ。Ecto単体で使った場合なのでphoenixと一緒に使った場合は違う部分があるかもしれません。

全体的にパイプライン演算子(`|>`)を存分に使ったコードで楽しいです。キーワードクエリはSQLっぽくてどうにも好きになれないので基本的にパイプラインクエリを使っています。

「こっちの方が適切」「もっとイケてる書き方がある」「phoenixではこう書ける」「phoenixでは使えない」等ありましたらコメントお願いします。

# 準備

phoenixであれば`mix phoenix.new`でこの辺りはだいたい勝手に生成されます。以下はEcto単体での話です。

`mix new blog --sup`でプロジェクトを作成。

依存に`ecto`と`postgrex`を追加

```mix.exs
  defp deps do
    [
      {:ecto, "~> 2.0"},
      {:postgrex, "0.11.2"}
    ]
  end
```

依存解決

```
$ mix deps.get
```

`mix.exs`のapplicationに`postgrex`追加

```mix.exs
  def application do
    [applications: [:logger, :postgrex],
     mod: {Blog, []}]
  end
```


設定ファイルの追記と`lib/blog/repo.ex`の生成。

```
$ mix ecto.gen.repo -r Blog.Repo
```

`config/config.exs`に設定を追加

```ex
config :blog, Blog.Repo,       
  adapter: Ecto.Adapters.Postgres,
  database: "blog_repo",
  username: "postgres",        
  password: "postgres",        
  hostname: "localhost"

config :blog, ecto_repos: [Blog.Repo]
```

`lib/blogs.ex`にスーパーバイザの設定

```ex
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      worker(Blog.Repo, []),
    ]

  ...
```

データベースの作成

```
$ mix ecto.create
```

# マイグレーション

```
$ mix ecto.gen.migration create_users
```

```priv/repo/migrations/xxxxxxx_create_users.exs
defmodule Blog.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :email, :string
      add :name, :string
      timestamps
    end
  end
end
```

```
$ mix ecto.gen.migration create_articles
```

```priv/repo/migraions/xxxxxxx_create_articles.exs
defmodule Blog.Repo.Migrations.CreateArticles do
  use Ecto.Migration

  def change do
    create table(:articles) do
      add :title, :string
      add :body, :text
      add :user_id, references(:users)
      timestamps
    end
  end
end
```

```
$ mix ecto.migrate
```

# スキーマ

```lib/blog/user.ex
defmodule Blog.User do
  use Ecto.Schema

  schema "users" do
    field :email, :string
    field :name, :string
    timestamps

    has_many :articles, Blog.Article
  end
end
```

```lib/blog/article.ex
defmodule Blog.Article do
  use Ecto.Schema 

  schema "articles" do
    field :title, :string
    field :body, :string
    timestamps

    belongs_to :user, Blog.User
  end
end
```

`schema`の中で`has_many`, `belongs_to`することでassociationを定義しています。

スキーマで使えるカラムのタイプは

* `:id`
* `:binary_id`
* `:integer`
* `:float`
* `:boolean`
* `:string`
* `:binary`
* `{:array, inner_type}`
* `:decimal`
* `:map`
* `{:map, inner_type}`

これらに加えて、カスタムタイプを作ることもできるようです。
デフォルトでは

* `:datetime`
* `:date`
* `:time`
* `:uuid`

があるようです。

# .iex.exs

iexで色々試す時に予め`import`, `alias`等をしたい場合は`.iex.exs`をカレントディレクトリかホームディレクトリに置くと読み込んでくれるのでiexでよく使うものはここに書いておきます。

```.iex.exs
import Ecto.Query, except: [preload: 2]
import Blog.Repo
alias Blog.User
alias Blog.Article
```

`import`, `require`, `use`, `alias`については以下を参考

> [alias, require, importの違いを理解してみる](http://qiita.com/syossan27/items/1325d2e5d40403d6fad5)
> [Elixir の require, imoport, use](http://qiita.com/taku_oka/items/61178122e7bd52188af1)

# ActiveRecord vs Ecto

## get, all, get_by

railsで言う`User.find(1)`

```ex
User |> get(1)
```

railsで言う`User.all`

```ex
User |> all
```

railsで言う`User.find_by(name: "国木田花丸")`

```ex
User |> get_by(name: "国木田花丸")
```

`get`, `all`, `get_by`を修飾名無し呼べるのは`.iex.exs`で`import Blog.Repo`しているためです。iex以外で使う時は`import Blog.Repo`するか、修飾名で呼んで下さい。

## クエリ構築 & 取得

railsで言う`User.where(name: "黒澤ルビィ")`

```ex
User |> where(name: "黒澤ルビィ") |> all
```

`where`は`import Ecto.Query`しているためです。

`User |> where(name: "zura")`でクエリを構築して、`all`でレコードを取得するイメージです。

クエリ構築で止めることで、合成可能なクエリを構築できます。

> 参考: [Elixir Ectoのスキーマ定義と合成可能なクエリ \- モジュールからデータベース用DSLへ](http://qiita.com/yoavlt/items/407ab562de5eab87bdb8)

もちろん`group_by`, `having`, `join`, `limit`, `offset`, `order_by`などもあります。

ちなみに、取得件数が1件の時は`all`の代わりに`one`を使います。

## Ecto2.0からのパイプラインによるクエリ構築 :tada:

railsで言う`Article.where(user_id: 1).order(:created_at).limit(5)`

```ex
Article |> where(user_id: 1) |> order_by(:inserted_at) |> limit(5)
# #Ecto.Query<from a in Blog.Article, where: a.user_id == 1,
# order_by: [asc: a.inserted_at], limit: 5>
```

`where`, `order_by`, `limit`は`import Ecto.Query`(ry。

ただし`select`する時は`select([a], a.body)`のようなスタイルにする必要があるようです(`select`自体あまり使うことはなさそうですが)

Ecto2.0以前のキーワードクエリやパイプラインクエリは妙に分かりづらいのでシンプルに表現できるこのスタイルは非常に嬉しいです。
ちなみにキーワードクエリと以前のパイプラインクエリによる同等のクエリ構築は以下のようです。

```ex
# キーワードクエリ
from a in Article, where: a.user_id == 1, order_by: [desc: a.inserted_at], limit: 5

# 以前のパイプラインクエリ
Article |> where([a], a.user_id == 1) |> order_by([a], desc: a.inserted_at) |> limit(5)
```

>参考 [Ecto v1\.1 released and Ecto v2\.0 plans « Plataformatec Blog](http://blog.plataformatec.com.br/2015/12/ecto-v1-1-released-and-ecto-v2-0-plans/)

## `has_many`のassociationを取り出す

railsで言う`user.articles`

```ex
user = User |> get(1)
articles = user |> Ecto.assoc(:articles) |> all
```

## associationをロードする

```ex
user = User |> get(1) |> preload(:articles)

user.articles
# [%Blog.Article{..}, ..]
```

`preload`は`import Blog.Repo`(ry
`Ecto.Query`にも`preload`という関数があるので、`.iex.exs`ではexceptしました。

## `belongs_to`のassociationを取り出す

railsで言う`article.user`

```ex
article = Article |> get(1) |> preload(:user)
user = article.user

# 一行で書ける
user = Article |> get(1) |> preload(:user) |> Map.get(:user)
```

なんというか惜しい。もうちょっと綺麗な書き方ないのですかね？

追記

```ex
Article |> get(1) |> Ecto.assoc(:user) |> one
```

でいけました。

## association構築

railsで言う`user.articles.new`

```ex
user = User |> get(1)
user |> Ecto.build_assoc(:articles)

# 同時にattributesも指定
user |> Ecto.build_assoc(:articles, title: "ruby", body: "がんばルビィ")
```

## insert & update & delete

railsで言う`User.create`, `User.update`, `User.delete`

```ex
# insert
%User{email: "zura@gmail.com", name: "国木田花丸"} |> insert

# update
%User{id: 1, email: "zura@gmail.com", name: "国木田花丸"} |> update

# delete
User |> get(1) |> delete
```

`get`, `get_by`, `insert`, `update`, `delete`には`!`を付けることで失敗時に例外を投げる関数になります。

phoenixでは`changeset`を使ったキャスト(railsのstrong parameterみたいなの)とバリデーションを行うのでいきなり`insert`することは少ないはず。phoenixでは`changeset`した結果を`insert`, `update`します。

## associationにinsert

railsで言う`user.article.create`

```ex
user = User |> get(1)
Ecto.build_assoc(user, :articles, title: "kwsmさん", body: "わかるわ") |> insert

# 一行で
User |> get(1) |> Ecto.build_assoc(:articles, title: "kwsmさん", body: "わかるわ") |> insert
```

phoenixでは途中で`changeset`して`insert`する感じ？

# 終わり
railsでよく使っていたものを中心にまとめました。この辺りが使えれば最低限のことはできるのではないでしょうか。

`preload`したものはモデルの中に保持されるので、以後データベースへのクエリは発生しません。
逆に`Ecto.assoc`は実行される度にデータベースへのクエリが発生します。

associationを何度も使うならば変数に入れておくか、`preload`しましょう。