Phoenixで既存のDjango製DB(Postgresql)に接続する

この記事は、[Elixir or Phoenix Advent Calendar 2018](https://qiita.com/advent-calendar/2018/elixir)の20日目の記事です。
昨日の記事は[@ma2ge](https://qiita.com/ma2ge)さんの[「Elixir School の日本語訳更新方法」](https://qiita.com/ma2ge/items/edb392ecadb6cfc3177e)でした。

# はじめに

既存アプリケーションのAPIとしてPhoenixを採用しました。既存アプリケーションはDjangoで動いているので、言語としてもフレームワークとしても一部の思想がかなり異なり、APIを開発していく中で多少の苦労があったのですが、ここではそのファーストステップであったDjangoのORマッパーで作成したPostgresqlのDBに接続するまでの記録を記事にしています。

## Schemaを作成する

ここでSchema(Model)を定義し、migrationファイルの生成を行います。ref:[mix phx.gen.schema](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Schema.html)

```zsh
❯ mix phx.gen.schema User users password:string last_login:naive_datetime is_superuser:boolean username:string first_name:string last_name:string email:string is_staff:boolean is_active:boolean date_joined:naive_datetime avatar:string --no-schema --table custom_user
* creating lib/myapp/user.ex
* creating priv/repo/migrations/20181206121950_create_custom_user.exs

Remember to update your repository by running migrations:

    $ mix ecto.migrate
```

- `User`がSchema名、`users`がDBのテーブル名となっています。上述の公式docを見るとわかるようにPhoenixのDDDの思想に従えば`Account.User`、`accounts_user`などとしてcontextのもとに配置するのがそのwayにのっているでしょうが、ここでは既存のアプリケーションの設計に従います。
- `password:string`などでカラム名とデータ型を定義しているのですが、`date_joined:naive_datetime`のようにPostgresqlの`timestamptz`型はEctoの`:naive_datetime`型（Elixirの`NaiveDateTime`型）に対応させています。ref:[Ecto.Schema](https://hexdocs.pm/ecto/Ecto.Schema.html)
- `--no-schema`オプションで既存DBにすでに定義されているカラムをmigrationファイルとして生成しないようにします。
- `--table`オプションでテーブル名を指定しています。 ※Djangoでデフォルトで生成されるユーザーのテーブルは`auth_user`だったりしますが、それを拡張して`custom_user`というテーブルを生成して実装していたのでこのテーブルを指定しています。
- これでSchemaの定義ファイルとmigrationファイルが生成されました。

migrationファイルなど確認し、migrateします。

```zsh
❯ mix ecto.migrate
```

## 確認

```zsh
❯ iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Myapp.Repo.get! Myapp.User, 1               
[debug] QUERY OK source="custom_user" db=15.7ms decode=4.1ms
SELECT c0."id", c0."avatar", c0."date_joined", c0."email", c0."first_name", c0."is_active", c0."is_staff", c0."is_superuser", c0."last_login", c0."last_name", c0."password", c0."username" FROM "custom_user" AS c0 WHERE (c0."id" = $1) [1]
%Myapp.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "custom_user">,
  avatar: "",
  date_joined: ~N[2018-12-03 15:46:08.436955],
  email: "admin@example.com",
  first_name: "",
  id: 1,
  is_active: true,
  is_staff: true,
  is_superuser: true,
  last_login: ~N[2018-12-03 15:46:27.053081],
  last_name: "",
  password: "***",
  username: "admin"
}
#>取れた😑

* ついでに子モデルへアクセス
iex(2)> user = Myapp.Repo.get! Myapp.User, 1       
[debug] QUERY OK source="custom_user" db=20.4ms queue=0.1ms
SELECT c0."id", c0."avatar", c0."date_joined", c0."email", c0."first_name", c0."is_active", c0."is_staff", c0."is_superuser", c0."last_login", c0."last_name", c0."password", c0."username" FROM "custom_user" AS c0 WHERE (c0."id" = $1) [1]
%Myapp.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "custom_user">,
  avatar: "",
  date_joined: ~N[2018-12-03 15:46:08.436955],
  email: "admin@example.com",
  first_name: "",
  hourly_wage: 0,
  id: 1,
  is_active: true,
  is_staff: true,
  is_superuser: true,
  last_login: ~N[2018-12-03 15:46:27.053081],
  last_name: "",
  password: "***",
  shifts: #Ecto.Association.NotLoaded<association :shifts is not loaded>,
  username: "admin"
}
#>userに代入して..

iex(3)> user.shifts
#Ecto.Association.NotLoaded<association :shifts is not loaded>
#>おっ、プリロードする必要があるらしい。

iex(4)> Myapp.Repo.all( Myapp.User) |> Myapp.Repo.preload(:shifts)
[debug] QUERY OK source="custom_user" db=20.8ms decode=0.2ms queue=0.2ms
SELECT c0."id", c0."avatar", c0."date_joined", c0."email", c0."first_name", c0."is_active", c0."is_staff", c0."is_superuser", c0."last_login", c0."last_name", c0."password", c0."username" FROM "custom_user" AS c0 []
[debug] QUERY OK source="shift" db=36.0ms
SELECT s0."id", s0."date", s0."opening_time", s0."closing_time", s0."user_id", s0."user_id" FROM "shift" AS s0 WHERE (s0."user_id" = $1) ORDER BY s0."user_id" [1]
[
  %Myapp.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "custom_user">,
    avatar: "",
    date_joined: ~N[2018-12-03 15:46:08.436955],
    email: "admin@example.com",
    first_name: "",
    id: 1,
    is_active: true,
    is_staff: true,
    is_superuser: true,
    last_login: ~N[2018-12-03 15:46:27.053081],
    last_name: "",
    password: "***",
    shifts: [
      %Myapp.Shift{
        __meta__: #Ecto.Schema.Metadata<:loaded, "shift">,
        closing_time: ~T[19:00:00.000000],
        date: ~D[2018-12-09],
        id: 1,
        opening_time: ~T[11:00:00.000000],
        user: #Ecto.Association.NotLoaded<association :user is not loaded>,
        user_id: 1
      }
    ],
    username: "admin"
  }
]
#>pipe operator💪
```

## まとめ
Ectoを介してDjango+Postgresqlの既存DBに接続することができました。API開発によって広がる可能性は大きいです。高級言語ながら並行処理・並列処理の扱いに長けたelixirとその長所を見事に活かすフレームワークPhoenix、きっと皆さんの開発の中でも適切な役割を担えると思います。

明日の担当は[@kobatako](https://qiita.com/kobatako)さんです。