技術メモ

プログラミングとか電子工作とか

Phoenix入門 (第12章 Ecto その1)

f:id:ysmn_deus:20190219130922p:plain

どうも、靖宗です。
今回はEcto、ということでデータベースラッパーのお話でしょうか。
Elixirでよく使われるライブラリなので個別に触りたいところでもありますが、とりあえず今回はPhoenixのドキュメントを読み進めます。

Ecto

ウェブシステムを開発する上で避けられないのがデータベース絡みのお話です。
(とはいえFirebaseなど便利なものも出てきているので、薄ぼんやりとした理解でもある程度のものができてしまう時代だとは思います。)
Ectoは現在

の5つのRDBMSに対応してるそうです。Mnesiaなんかは初耳です。
特に何も指定しない場合はPhoenixPostgreSQLをチョイスします。

Hello, Ecto

開発用のPostgreSQL

最初のUp and Runningでも触れましたが開発用のデータベースの設定はconfig/dev.exsに記載されてます。ユーザー名や参照先を変更したい場合はこのファイルを編集します。
デフォルトでは参照先はlocalhost、ユーザー名はpostgres、パスワードはpostgresとなっています。このpostgresのユーザーを作成するにはpsqlコマンドでrootユーザーとしてログインした後下記のコマンドで作成できます。

CREATE USER postgres;
ALTER USER postgres PASSWORD 'postgres';
ALTER USER postgres WITH SUPERUSER;

簡単に触ってみる

とりあえずなんか動かしてみます。mix phx.gen.schemaでEctoのスキーマを作成できるそうです。
EctoのスキーマはElixirのデータタイプをPostgreSQLのテーブルへと変換するルールのようなものでしょうか。
とりあえずドキュメントどおりやってみます。

PS \hello_phoenix> mix phx.gen.schema User users name:string email:string bio:string number_of_pets:integer
* creating lib/hello_phoenix/user.ex
* creating priv/repo/migrations/20190321014808_create_users.exs

Remember to update your repository by running migrations:

    $ mix ecto.migrate

ファイルが2個生成され、始めてlib/hello_phoenix/の方にファイルができました。なんか嬉しい。
1個目は先ほど定義したスキーマの情報が記載されています。2個目は名前や中身的に現在のデータベースに先ほど定義した情報のテーブルを作成するための記述でしょう。ORMとかでよくありそうな感じです。

スキーマを登録したらマイグレーション(データベース作ったりリレーションの整合性とったりとか)が必要な筈です。もしmix ecto.createしてなければおそらくそちらでスキーマの取り込みまで実行されると思いますが、基本的に開発中にどんどん追加していくと思いますので、mix ecto.migrateマイグレーションします。

PS \hello_phoenix> mix ecto.migrate
Compiling 1 file (.ex)
Generated hello_phoenix app
[info] == Running 20190321014808 HelloPhoenix.Repo.Migrations.CreateUsers.change/0 forward
[info] create table users
[info] == Migrated 20190321014808 in 0.0s

データベースにusersのテーブルが作成されているか確認します。
自分はdockerでpostgresを動かしてるのでdockerから確認します。

PS \hello_phoenix> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
c1b71c3bc403        postgres            "docker-entrypoint.s…"   3 weeks ago         Up 3 days           0.0.0.0:5432->5432/tcp   ph_psql
PS \hello_phoenix> docker exec -it ph_psql /bin/bash
root@c1b71c3bc403:/# psql -U elixir
psql (10.5 (Debian 10.5-1.pgdg90+1))
Type "help" for help.

elixir=# \connect hello_phoenix_dev
You are now connected to database "hello_phoenix_dev" as user "elixir".
hello_phoenix_dev=# \d
               List of relations
 Schema |       Name        |   Type   | Owner
--------+-------------------+----------+--------
 public | schema_migrations | table    | elixir
 public | users             | table    | elixir
 public | users_id_seq      | sequence | elixir
(3 rows)

テーブル作成されていることが確認できます。
usersだけでなくusers_id_seqというテーブルもできていますが、コレはusersのシーケンスだそうです。いままでDjangoなどのORMに頼りっきりでPostgreSQL使ってるのにシーケンスという概念を知りませんでしたが、どうやらユニークなIDを生成するオブジェクトだそうで、ユーザー情報を生成するときに連番の番号を高速に生成して登録したり検索の時によしなにしてくれるもんだと思っておけばいいんじゃないでしょうか(適当)
一応usersの中身も見ておきます。

hello_phoenix_dev=# \d users
                                            Table "public.users"
     Column     |              Type              | Collation | Nullable |              Default
----------------+--------------------------------+-----------+----------+-----------------------------------
 id             | bigint                         |           | not null | nextval('users_id_seq'::regclass)
 name           | character varying(255)         |           |          |
 email          | character varying(255)         |           |          |
 bio            | character varying(255)         |           |          |
 number_of_pets | integer                        |           |          |
 inserted_at    | timestamp(0) without time zone |           | not null |
 updated_at     | timestamp(0) without time zone |           | not null |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)

idは先ほど説明したシーケンスの物なのでいいとして、inserted_atupdated_atが着いてます。(ORM使いまくりマンとしては有り難いです)
これは先ほどmix phx.gen.schemaで生成された一個目のファイルusers.exに記載があります。

...
  schema "users" do
    field :bio, :string
    field :email, :string
    field :name, :string
    field :number_of_pets, :integer

    timestamps()
  end
...

この辺をカスタマイズしたい欲求はあまり無いと思いますが、変更するならこの辺をいじるっぽいです。
プライマリキーを変更する方法はまだ特に記載がありませんが、基本デフォルトでいい気はします。

The Repo

Phoenixでアプリケーションを生成した際に生成されるファイルlib/hello_phoenix/repo.exに関してです。
中身は非常にシンプルでものの数行しかありません。

defmodule HelloPhoenix.Repo do
  use Ecto.Repo,
    otp_app: :hello_phoenix,
    adapter: Ecto.Adapters.Postgres
end

生成したプロジェクトでEctoを使うで!アダプターはPostgresで!ってぐらいの役割なのでほとんど編集することは無いと思います。
あるとするならPostgreSQL以外の選択肢をチョイスした場合にアダプターを変更するぐらいでしょうか?

あとデータベースの設定はconfig/dev.exsを編集しましたが、必要に応じてテスト用の設定config/test.exsやデプロイ用の設定config/prod.secret.exsを編集するようです。

The Schema

一番最初にスキーマの作成をしましたが、EctoのスキーマはElixirのデータ構造とデータベースのテーブルを繋ぐ(変換する)役割を果たしています。
上記の例ではlib/hello_phoenix/user.exスキーマが作成されています。

defmodule HelloPhoenix.User do
  use Ecto.Schema
  import Ecto.Changeset


  schema "users" do
    field :bio, :string
    field :email, :string
    field :name, :string
    field :number_of_pets, :integer

    timestamps()
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :bio, :number_of_pets])
    |> validate_required([:name, :email, :bio, :number_of_pets])
  end
end

どうやらスキーマで定義した型は構造体として利用できるそうです。
データのキャストからバリデーションもこのスキーマのモジュールが担ってくれるようなので、開発している際はバリデーションをいちいち書く必要は無さそうです。

一旦この辺で区切ります。
次回はChangesetsやValidationsのお話。リレーションを貼ったりする方法とかあるのかな