この記事は、Elixir or Phoenix Advent Calendar 2018の20日目の記事です。
昨日の記事は@ma2geさんの「Elixir School の日本語訳更新方法」でした。
はじめに
既存アプリケーションのAPIとしてPhoenixを採用しました。既存アプリケーションはDjangoで動いているので、言語としてもフレームワークとしても一部の思想がかなり異なり、APIを開発していく中で多少の苦労があったのですが、ここではそのファーストステップであったDjangoのORマッパーで作成したPostgresqlのDBに接続するまでの記録を記事にしています。
Schemaを作成する
ここでSchema(Model)を定義し、migrationファイルの生成を行います。ref:mix phx.gen.schema
❯ 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 -
--no-schema
オプションで既存DBにすでに定義されているカラムをmigrationファイルとして生成しないようにします。 -
--table
オプションでテーブル名を指定しています。 ※Djangoでデフォルトで生成されるユーザーのテーブルはauth_user
だったりしますが、それを拡張してcustom_user
というテーブルを生成して実装していたのでこのテーブルを指定しています。 - これでSchemaの定義ファイルとmigrationファイルが生成されました。
migrationファイルなど確認し、migrateします。
❯ mix ecto.migrate
確認
❯ 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さんです。