Chapter 14

Mixタスク

koga1020
koga1020
2021.11.27に更新

Mixタスク

現在、新しく生成されたアプリケーションには、Phoenix固有およびEcto固有のMixタスク多数組み込まれています。また、アプリケーション独自のタスクを作成することもできます。

注意: mix について詳しく知りたい方は、Elixirの公式のMix入門を参照してください。

Phoenixタスク

$ mix help --search "phx"
mix local.phx          # Updates the Phoenix project generator locally
mix phx                # Prints Phoenix help information
mix phx.digest         # Digests and compresses static files
mix phx.digest.clean   # Removes old versions of static assets.
mix phx.gen.auth       # Generates authentication logic for a resource
mix phx.gen.cert       # Generates a self-signed certificate for HTTPS testing
mix phx.gen.channel    # Generates a Phoenix channel
mix phx.gen.context    # Generates a context with functions around an Ecto schema
mix phx.gen.embedded   # Generates an embedded Ecto schema file
mix phx.gen.html       # Generates controller, views, and context for an HTML resource
mix phx.gen.json       # Generates controller, views, and context for a JSON resource
mix phx.gen.live       # Generates LiveView, templates, and context for a resource
mix phx.gen.notifier   # Generates a notifier that delivers emails by default
mix phx.gen.presence   # Generates a Presence tracker
mix phx.gen.schema     # Generates an Ecto schema and migration file
mix phx.gen.secret     # Generates a secret
mix phx.gen.socket     # Generates a Phoenix socket handler
mix phx.new            # Creates a new Phoenix application
mix phx.new.ecto       # Creates a new Ecto project within an umbrella project
mix phx.new.web        # Creates a new Phoenix web project within an umbrella project
mix phx.routes         # Prints all routes
mix phx.server         # Starts applications and their servers

ガイドの中でも1度は目にしたことがありますが、それらの情報が1箇所に持っていることは良いことのように思えます。

ここでは、Phoenixのインストーラーに含まれる phx.newphx.new.ectophx.new.web を除く、すべてのPhoenix Mixタスクを取り上げます。これらのタスクやその他のタスクについての詳細は、mix help TASK を呼び出すことで詳しく知ることができます。

mix phx.gen.html

Phoenixは、完全なHTMLリソースを立ち上げるためのすべてのコードを生成する機能を提供します。生成されるのはEctoマイグレーション、 Ectoコンテキスト、必要なすべてのアクション、ビュー、テンプレートを持つコントローラーです。これは、とてつもなく時間を節約できます。これを実現する方法を見てみましょう。

mix phx.gen.html タスクは次の引数を取ります。コンテキストのモジュール名、スキーマのモジュール名、リソース名、column_name:type属性のリストです。ここで渡すモジュール名は、適切な大文字に続いて、Elixirのモジュール名のルールに準拠していなければなりません。

$ mix phx.gen.html Blog Post posts body:string word_count:integer
* creating lib/hello_web/controllers/post_controller.ex
* creating lib/hello_web/templates/post/edit.html.heex
* creating lib/hello_web/templates/post/form.html.heex
* creating lib/hello_web/templates/post/index.html.heex
* creating lib/hello_web/templates/post/new.html.heex
* creating lib/hello_web/templates/post/show.html.heex
* creating lib/hello_web/views/post_view.ex
* creating test/hello_web/controllers/post_controller_test.exs
* creating lib/hello/blog/post.ex
* creating priv/repo/migrations/20211001233016_create_posts.exs
* creating lib/hello/blog.ex
* injecting lib/hello/blog.ex
* creating test/hello/blog_test.exs
* injecting test/hello/blog_test.exs
* creating test/support/fixtures/blog_fixtures.ex
* injecting test/support/fixtures/blog_fixtures.ex

ファイルの作成が終わると、mix phx.gen.html は、Ectoマイグレーションを実行するのと同様に、ルーターファイルに1行を追加する必要があることを教えてくれます。

Add the resource to your browser scope in lib/hello_web/router.ex:

    resources "/posts", PostController

Remember to update your repository by running migrations:

    $ mix ecto.migrate

重要: これを行わないと、ログに以下のような警告が表示され、関数を実行しようとするとアプリケーションがエラーになります。

$ mix phx.server
Compiling 17 files (.ex)

warning: function HelloWeb.Router.Helpers.post_path/3 is undefined or private
  lib/hello_web/controllers/post_controller.ex:22:

リソースのコンテキストやスキーマを作成したくない場合は、--no-context フラグを使うことができます。この場合でも、パラメーターとしてコンテキストモジュール名が必要になることに注意しましょう。

$ mix phx.gen.html Blog Post posts body:string word_count:integer --no-context
* creating lib/hello_web/controllers/post_controller.ex
* creating lib/hello_web/templates/post/edit.html.heex
* creating lib/hello_web/templates/post/form.html.heex
* creating lib/hello_web/templates/post/index.html.heex
* creating lib/hello_web/templates/post/new.html.heex
* creating lib/hello_web/templates/post/show.html.heex
* creating lib/hello_web/views/post_view.ex
* creating test/hello_web/controllers/post_controller_test.exs

ルーターファイルに行を追加する必要があることを教えてくれますが、コンテキストをスキップしたので、ecto.migrate については何も言及してくれません。

Add the resource to your browser scope in lib/hello_web/router.ex:

    resources "/posts", PostController

同様に、リソースのスキーマなしでコンテキストを作成したい場合は --no-schema フラグを使うことができます。

$ mix phx.gen.html Blog Post posts body:string word_count:integer --no-schema
* creating lib/hello_web/controllers/post_controller.ex
* creating lib/hello_web/templates/post/edit.html.heex
* creating lib/hello_web/templates/post/form.html.heex
* creating lib/hello_web/templates/post/index.html.heex
* creating lib/hello_web/templates/post/new.html.heex
* creating lib/hello_web/templates/post/show.html.heex
* creating lib/hello_web/views/post_view.ex
* creating test/hello_web/controllers/post_controller_test.exs
* creating lib/hello/blog.ex
* injecting lib/hello/blog.ex
* creating test/hello/blog_test.exs
* injecting test/hello/blog_test.exs
* creating test/support/fixtures/blog_fixtures.ex
* injecting test/support/fixtures/blog_fixtures.ex

ルーターファイルに行を追加する必要があることを教えてくれますが、スキーマをスキップしているので、 ecto.migrate については何も言及していません。

mix phx.gen.json

また、Phoenixは、完全なJSONリソースを立ち上げるためのすべてのコードを生成する機能を提供しています。生成されるのはEctoマイグレーション、Ectoスキーマ、すべての必要なアクションとビューを持つコントローラーです。このコマンドは、アプリのテンプレートを作成しません。

mix phx.gen.json タスクは次の引数を取ります: コンテキストのモジュール名、スキーマのモジュール名、リソース名、column_name:type属性のリストです。ここで渡すモジュール名は、適切な大文字から始まり、Elixirのモジュール名のルールに準拠していなければなりません。

$ mix phx.gen.json Blog Post posts title:string content:string
* creating lib/hello_web/controllers/post_controller.ex
* creating lib/hello_web/views/post_view.ex
* creating test/hello_web/controllers/post_controller_test.exs
* creating lib/hello_web/views/changeset_view.ex
* creating lib/hello_web/controllers/fallback_controller.ex
* creating lib/hello/blog/post.ex
* creating priv/repo/migrations/20170906153323_create_posts.exs
* creating lib/hello/blog.ex
* injecting lib/hello/blog.ex
* creating test/hello/blog/blog_test.exs
* injecting test/hello/blog/blog_test.exs
* creating test/support/fixtures/blog_fixtures.ex
* injecting test/support/fixtures/blog_fixtures.ex

mix phx.gen.json がファイルの作成を終えると、Ectoマイグレーションを実行するのと同様に、ルーターファイルに1行を追加する必要があることを教えてくれます。

Add the resource to your :api scope in lib/hello_web/router.ex:

    resources "/posts", PostController, except: [:new, :edit]

Remember to update your repository by running migrations:

    $ mix ecto.migrate

重要: これを行わないと、ログに以下のような警告が表示され、ページを読み込もうとするとアプリケーションがエラーになります。

$ mix phx.server
Compiling 19 files (.ex)

warning: function HelloWeb.Router.Helpers.post_path/3 is undefined or private
  lib/hello_web/controllers/post_controller.ex:18

mix phx.gen.jsonmix phx.gen.html と同様に --no-context, --no-schema などもサポートしています。

mix phx.gen.context

完全な HTML/JSON リソースを必要とせず、コンテキストだけが必要な場合は、 mix phx.gen.context タスクを使用します。このタスクは、コンテキスト、スキーマ、マイグレーション、テストケースを生成します。

mix phx.gen.context タスクは次の引数を取ります。コンテキストのモジュール名、スキーマのモジュール名、リソース名、および column_name:type 属性のリストです。

$ mix phx.gen.context Accounts User users name:string age:integer
* creating lib/hello/accounts/user.ex
* creating priv/repo/migrations/20170906161158_create_users.exs
* creating lib/hello/accounts.ex
* injecting lib/hello/accounts.ex
* creating test/hello/accounts/accounts_test.exs
* injecting test/hello/accounts/accounts_test.exs
* creating test/support/fixtures/accounts_fixtures.ex
* injecting test/support/fixtures/accounts_fixtures.ex

注意: リソースを名前空間にする必要がある場合は、単にジェネレーターの第一引数を名前空間にできます。

$ mix phx.gen.context Admin.Accounts User users name:string age:integer
* creating lib/hello/admin/accounts/user.ex
* creating priv/repo/migrations/20170906161246_create_users.exs
* creating lib/hello/admin/accounts.ex
* injecting lib/hello/admin/accounts.ex
* creating test/hello/admin/accounts_test.exs
* injecting test/hello/admin/accounts_test.exs
* creating test/support/fixtures/admin/accounts_fixtures.ex
* injecting test/support/fixtures/admin/accounts_fixtures.ex

mix phx.gen.schema

完全なHTML/JSONリソースを必要とせず、コンテキストの生成や変更に興味がない場合、mix phx.gen.schema タスクを使うことができます。これはスキーマとマイグレーションを生成します。

mix phx.gen.schema タスクは次の引数を取ります: スキーマのモジュール名 (名前付きの場合もあります)、リソース名、および column_name:type 属性のリストです。

$ mix phx.gen.schema Accounts.Credential credentials email:string:unique user_id:references:users
* creating lib/hello/accounts/credential.ex
* creating priv/repo/migrations/20170906162013_create_credentials.exs

mix phx.gen.auth

Phoenixには、Ectoマイグレーション、Phoenixコンテキスト、コントローラー、テンプレートなど、完全な認証システムを立ち上げるためのすべてのコードを生成する機能もあります。これは非常に大きな時間の節約になり、システムに認証を素早く追加して、アプリケーションが解決しようとしている主要な問題に焦点を戻すことができます。

mix phx.gen.auth タスクは次のような引数をとります。コンテキストのモジュール名、スキーマのモジュール名、そしてデータベーステーブルやルートヘルパーの生成に使用するスキーマ名の複数形です。

コマンドのサンプルは次の通りです。

$ mix phx.gen.auth Accounts User users
* creating priv/repo/migrations/20201205184926_create_users_auth_tables.exs
* creating lib/hello/accounts/user_notifier.ex
* creating lib/hello/accounts/user.ex
* creating lib/hello/accounts/user_token.ex
* creating lib/hello_web/controllers/user_auth.ex
* creating test/hello_web/controllers/user_auth_test.exs
* creating lib/hello_web/views/user_confirmation_view.ex
* creating lib/hello_web/templates/user_confirmation/new.html.heex
* creating lib/hello_web/templates/user_confirmation/edit.html.heex
* creating lib/hello_web/controllers/user_confirmation_controller.ex
* creating test/hello_web/controllers/user_confirmation_controller_test.exs
* creating lib/hello_web/templates/layout/_user_menu.html.heex
* creating lib/hello_web/templates/user_registration/new.html.heex
* creating lib/hello_web/controllers/user_registration_controller.ex
* creating test/hello_web/controllers/user_registration_controller_test.exs
* creating lib/hello_web/views/user_registration_view.ex
* creating lib/hello_web/views/user_reset_password_view.ex
* creating lib/hello_web/controllers/user_reset_password_controller.ex
* creating test/hello_web/controllers/user_reset_password_controller_test.exs
* creating lib/hello_web/templates/user_reset_password/edit.html.heex
* creating lib/hello_web/templates/user_reset_password/new.html.heex
* creating lib/hello_web/views/user_session_view.ex
* creating lib/hello_web/controllers/user_session_controller.ex
* creating test/hello_web/controllers/user_session_controller_test.exs
* creating lib/hello_web/templates/user_session/new.html.heex
* creating lib/hello_web/views/user_settings_view.ex
* creating lib/hello_web/templates/user_settings/edit.html.heex
* creating lib/hello_web/controllers/user_settings_controller.ex
* creating test/hello_web/controllers/user_settings_controller_test.exs
* creating lib/hello/accounts.ex
* injecting lib/hello/accounts.ex
* creating test/hello/accounts_test.exs
* injecting test/hello/accounts_test.exs
* creating test/support/fixtures/accounts_fixtures.ex
* injecting test/support/fixtures/accounts_fixtures.ex
* injecting test/support/conn_case.ex
* injecting config/test.exs
* injecting mix.exs
* injecting lib/hello_web/router.ex
* injecting lib/hello_web/router.ex - imports
* injecting lib/hello_web/router.ex - plug
* injecting lib/hello_web/templates/layout/root.html.heex

mix phx.gen.auth がファイルを作成し終わると、Ectoマイグレーションを実行して依存関係を再取得する必要があることを親切に教えてくれます。

Please re-fetch your dependencies with the following command:

    mix deps.get

Remember to update your repository by running migrations:

  $ mix ecto.migrate

Once you are ready, visit "/users/register"
to create your account and then access to "/dev/mailbox" to
see the account confirmation email.

このジェネレーターを使い始めるためのより詳細な手順は、mix phx.gen.auth 認証ガイド を参照してください。

mix phx.gen.channel and mix phx.gen.socket

このタスクは、基本的なPhoenixチャンネルと、チャンネルを動作させるためのソケット(まだ作成していない場合)、およびそのテストケースを生成します。このタスクはチャンネルのモジュール名を唯一の引数として受け取ります。

$ mix phx.gen.channel Room
* creating lib/hello_web/channels/room_channel.ex
* creating test/hello_web/channels/room_channel_test.exs

アプリケーションがまだ UserSocket を持っていない場合は、作成するかどうかを尋ねてきます。

The default socket handler - HelloWeb.UserSocket - was not found
in its default location.

Do you want to create it? [Y/n]

確認を済ませると、チャンネルが作成されますので、エンドポイントにソケットを接続してください。

Add the socket handler to your `lib/hello_web/endpoint.ex`, for example:

    socket "/socket", HelloWeb.UserSocket,
      websocket: true,
      longpoll: false

For the front-end integration, you need to import the `user_socket.js`
in your `assets/js/app.js` file:

    import "./user_socket.js"

すでに UserSocket が存在していたり、作成しないと決めた場合には、channel ジェネレーターは、手動でSocketに追加するように指示します。

Add the channel to your `lib/hello_web/channels/user_socket.ex` handler, for example:

    channel "rooms:lobby", HelloWeb.RoomChannel

また、mix phx.gen.socket を起動することで、いつでもソケットを作成できます。

mix phx.gen.presence

このタスクはプレゼンストラッカーを生成します。引数としてモジュール名を渡すことができ、モジュール名を渡さない場合は Presence が用いられます。

$ mix phx.gen.presence Presence
* lib/hello_web/channels/presence.ex

Add your new module to your supervision tree,
in lib/hello/application.ex:

    children = [
      ...
      HelloWeb.Presence
    ]

mix phx.routes

このタスクの目的は1つで、あるルーターに対して定義されたすべてのルートを表示することです。ルーティングガイドで広く使われているのを見ました。

このタスクにルーターを指定しない場合、Phoenixが生成してくれたルーターがデフォルトになります。

$ mix phx.routes
page_path  GET  /  TaskTester.PageController.index/2

また、アプリケーションに複数のルーターがある場合は、個々のルーターを指定することもできます。

$ mix phx.routes TaskTesterWeb.Router
page_path  GET  /  TaskTesterWeb.PageController.index/2

mix phx.server

これはアプリケーションを起動するために使用するタスクです。これは引数を一切取りません。何か引数を渡しても、それは静かに無視されます。

$ mix phx.server
[info] Running TaskTesterWeb.Endpoint with Cowboy on port 4000 (http)

引数 DoesNotExist を黙って無視します。

$ mix phx.server DoesNotExist
[info] Running TaskTesterWeb.Endpoint with Cowboy on port 4000 (http)

アプリケーションを起動して IEx セッションを開きたい場合は、iex 内でMixタスクを次のように実行できます。

$ iex -S mix phx.server
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

[info] Running TaskTesterWeb.Endpoint with Cowboy on port 4000 (http)
Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

mix phx.digest

このタスクは2つのことを行います。静的アセットのダイジェストを作成し、圧縮します。

ここでいう「ダイジェスト」とは、アセットの内容をMD5によってダイジェストしたもので、アセットのファイル名に追加されます。これにより、一種のフィンガープリントが作成されます。ダイジェストが変更されなければ、ブラウザやCDNはキャッシュされたバージョンを使用します。ダイジェストが変更された場合は、新しいバージョンを再取得します。

このタスクを実行する前に、helloアプリケーションの2つのディレクトリの内容を調べてみましょう。

最初の priv/static/ は次のようになっているはずです。

├── images
│   └── phoenix.png
└── robots.txt

そして、assets/ は次のようになります。

├── css
│   └── app.css
├── js
│   └── app.js
└── vendor
    └── phoenix.js

これらのファイルはすべて静的アセットです。それでは mix phx.digest タスクを実行してみましょう。

$ mix phx.digest
Check your digested files at 'priv/static'.

タスクが示すように、priv/static/ ディレクトリの内容を検査できます。 assets/ のすべてのファイルが priv/static/ にコピーされ、各ファイルにはいくつかのバージョンがあることがわかります。それらのバージョンは次の通りです。

  • 元ファイル
  • gzip圧縮されたファイル
  • 元のファイル名とそのダイジェストを含むファイル
  • ファイル名とそのダイジェストを含む圧縮ファイル

オプションで、設定ファイルの :gzippable_exts オプションを使って、どのファイルを圧縮するかを決めることができます。

config :phoenix, :gzippable_exts, ~w(.js .css)

注意: mix phx.digest が処理されたファイルを置く別の出力フォルダーを指定できます。第一引数は静的ファイルが置かれているパスです。

$ mix phx.digest priv/static/ -o www/public/
Check your digested files at 'www/public/'

注意: mix phx.digest.clean を使用して、古いバージョンのアセットを削除できます。生成されたすべてのファイルを削除したい場合は、 mix phx.digest.clean --all を実行してください。

Ectoタスク

新しく生成されたPhoenixアプリケーションは、デフォルトでEctoとPostgrexを依存関係として含んでいます(mix phx.new--no-ecto フラグを付けて使わない限り)。これらの依存関係には、一般的なEctoの操作を行うためのMixタスクが含まれています。どのようなタスクがあるのか見てみましょう。

$ mix help --search "ecto"
mix ecto               # Prints Ecto help information
mix ecto.create        # Creates the repository storage
mix ecto.drop          # Drops the repository storage
mix ecto.dump          # Dumps the repository database structure
mix ecto.gen.migration # Generates a new migration for the repo
mix ecto.gen.repo      # Generates a new repository
mix ecto.load          # Loads previously dumped database structure
mix ecto.migrate       # Runs the repository migrations
mix ecto.migrations    # Displays the repository migration status
mix ecto.reset         # Alias defined in mix.exs
mix ecto.rollback      # Rolls back the repository migrations
mix ecto.setup         # Alias defined in mix.exs

注意: アプリケーションを起動せずにタスクを実行するには、--no-start フラグを付けて上記のタスクを実行できます。

mix ecto.create

このタスクはレポで指定されたデータベースを作成します。デフォルトでは、アプリケーションの名前のついたレポ(Ectoをオプトアウトしていない限り、アプリケーションで生成されたもの)を探しますが、必要に応じて別のレポを渡すこともできます。

実際の動作は次のようになります。

$ mix ecto.create
The database for Hello.Repo has been created.

ecto.create でうまくいかないことがいくつかあります。Postgresデータベースに "postgres" ロール(ユーザー)がない場合、このようなエラーが発生します。

$ mix ecto.create
** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  role "postgres" does not exist

ログインしてデータベースを作成するために必要なパーミッションを持った "postgres" ロールを psql コンソールにて作成することで、これを修正できます。

=# CREATE ROLE postgres LOGIN CREATEDB;
CREATE ROLE

"postgres" ロールがアプリケーションにログインする権限を持っていない場合、このようなエラーが発生します。

$ mix ecto.create
** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  role "postgres" is not permitted to log in

これを修正するには、"postgres" ユーザーのパーミッションを変更してログインを許可する必要があります。

=# ALTER ROLE postgres LOGIN;
ALTER ROLE

"postgres" ロールがデータベースを作成する権限を持っていない場合、このようなエラーが発生します。

$ mix ecto.create
** (Mix) The database for Hello.Repo couldn't be created, reason given: ERROR:  permission denied to create database

これを修正するには、psql コンソールで "postgres" ユーザーのパーミッションを変更して、データベースの作成を許可する必要があります。

=# ALTER ROLE postgres CREATEDB;
ALTER ROLE

"postgres" ロールがデフォルトの "postgres" とは異なるパスワードを使用している場合、このエラーが発生します。

$ mix ecto.create
** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  password authentication failed for user "postgres"

これを修正するには、環境固有の設定ファイルでパスワードを変更します。開発環境で使用するパスワードは config/dev.exs ファイルの一番下にあります。

最後に、データベースを作成したい OurCustom.Repo という名前のレポがある場合は、次のように実行します。

$ mix ecto.create -r OurCustom.Repo
The database for OurCustom.Repo has been created.

mix ecto.drop

このタスクはレポで指定したデータベースを削除します。デフォルトでは、私たちのアプリケーションにちなんだレポを探します(Ectoをオプトアウトしない限り、私たちのアプリケーションで生成されたもの)。dbを削除するかどうかを確認するためのプロンプトは表示されませんので、注意してください。

$ mix ecto.drop
The database for Hello.Repo has been dropped.

たまたまデータベースを削除したいレポがあった場合は、-r フラグで指定します。

$ mix ecto.drop -r OurCustom.Repo
The database for OurCustom.Repo has been dropped.

mix ecto.gen.repo

多くのアプリケーションでは複数のデータストアを必要とします。それぞれのデータストアに対して新しいレポが必要で、ecto.gen.repo で自動的に生成できます。

レポの名前を OurCustom.Repo とすると、このタスクは lib/our_custom/repo.ex という名前でレポを作成します。

$ mix ecto.gen.repo -r OurCustom.Repo
* creating lib/our_custom
* creating lib/our_custom/repo.ex
* updating config/config.exs
Don't forget to add your new repo to your supervision tree
(typically in lib/hello/application.ex):

    {OurCustom.Repo, []}

このタスクは config/config.exs を更新していることに注目してください。見てみると、新しいレポ用に追加された設定ブロックが見えます。

. . .
config :hello, OurCustom.Repo,
  database: "hello_repo",
  username: "user",
  password: "pass",
  hostname: "localhost"
. . .

もちろん、ログインクレデンシャルをデータベースが期待するものと一致するように変更する必要があります。また、他の環境用に設定を変更する必要があります。

指示にしたがって、新しいレポをスーパーバイザーツリーに追加する必要があります。私たちの Hello アプリケーションでは、lib/hello/application.ex を開き、私たちのレポをworkerとして children リストに追加します。

. . .
children = [
  # Start the Ecto repository
  Hello.Repo,
  # Our custom repo
  OurCustom.Repo,
  # Start the endpoint when the application starts
  HelloWeb.Endpoint,
]
. . .

mix ecto.gen.migration

マイグレーションは、データベーススキーマへの変更に影響を与えるためのプログラム的で繰り返し可能な方法です。マイグレーションは単なるモジュールであり、ecto.gen.migration タスクで作成できます。新しいコメントテーブルのためのマイグレーションを作成する手順を見てみましょう。

タスクを起動するには、必要なモジュール名のsnake_caseバージョンを指定するだけです。この名前にはマイグレーションで何をするかを記述することが望ましいです。

$ mix ecto.gen.migration add_comments_table
* creating priv/repo/migrations
* creating priv/repo/migrations/20150318001628_add_comments_table.exs

マイグレーションのファイル名は、ファイルが作成された日時を表す文字列で始まることに注目してください。

priv/repo/migrations/20150318001628_add_comments_table.exs にある ecto.gen.migration が生成したファイルを見てみましょう。

defmodule Hello.Repo.Migrations.AddCommentsTable do
  use Ecto.Migration

  def change do
  end
end

単一の関数 change/0 があり、これはフォワードとロールバックの両方を処理します。Ectoの便利なDSLを使ってスキーマの変更を定義し、ロールフォワードかロールバックかによって何をすべきかを判断します。非常に良いですね。

ここでやりたいことは、body カラム、word_count カラム、そして inserted_atupdated_at のタイムスタンプカラムを持つ comments テーブルを作成することです。

. . .
def change do
  create table(:comments) do
    add :body, :string
    add :word_count, :integer
    timestamps()
  end
end
. . .

繰り返しになりますが、このタスクは -r フラグと必要に応じて別のレポを使って実行できます。

$ mix ecto.gen.migration -r OurCustom.Repo add_users
* creating priv/repo/migrations
* creating priv/repo/migrations/20150318172927_add_users.exs

データベースのスキーマを変更する方法の詳細については、EctoのマイグレーションDSLのドキュメントを参照してください。
たとえば、既存のスキーマを変更するには、Ectoのalter/2のドキュメントをご覧ください。

これで完了です!マイグレーションを実行する準備ができました。

mix ecto.migrate

マイグレーションモジュールの準備ができたら、mix ecto.migrate を実行して、変更をデータベースに適用します。

$ mix ecto.migrate
[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward
[info] create table comments
[info] == Migrated in 0.1s

最初に ecto.migrate を実行すると、schema_migrations というテーブルが作成されます。これは、マイグレーションのファイル名のタイムスタンプ部分を保存することで、実行したすべてのマイグレーションを追跡します。

テーブル schema_migrations は次のようになっています。

hello_dev=# select * from schema_migrations;
version        |     inserted_at
---------------+---------------------
20150317170448 | 2015-03-17 21:07:26
20150318001628 | 2015-03-18 01:45:00
(2 rows)

マイグレーションをロールバックすると、ecto.rollback はそのマイグレーションを表すレコードを schema_migrations から削除します。

デフォルトでは、ecto.migrate は保留中のすべてのマイグレーションを実行します。タスクの実行時にいくつかのオプションを指定することで、どのマイグレーションを実行するかを制御できます。

-n または --step オプションを用いて、実行したい保留中のマイグレーションの数を指定できます。

$ mix ecto.migrate -n 2
[info] == Running Hello.Repo.Migrations.CreatePost.change/0 forward
[info] create table posts
[info] == Migrated in 0.0s
[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward
[info] create table comments
[info] == Migrated in 0.0s

--step オプションも同じように動作します。

mix ecto.migrate --step 2

to オプションは、指定されたバージョンまでのすべてのマイグレーションを実行します。

mix ecto.migrate --to 20150317170448

mix ecto.rollback

ecto.rollback タスクは最後に実行したマイグレーションを逆にして、スキーマの変更を元に戻します。ecto.migrateecto.rollback は互いにミラーイメージです。

$ mix ecto.rollback
[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 backward
[info] drop table comments
[info] == Migrated in 0.0s

ecto.rollbackecto.migrate と同じオプションを扱うので、-n, --step, -v, --toecto.migrate と同じように動作します。

独自のMixタスクを作成する

このガイドで見てきたように、Mix自体も、アプリケーションに持ち込む依存関係も、本当に便利なタスクを無料で提供してくれます。これらのいずれも、個々のアプリケーションのすべてのニーズを予測することはできませんが、Mixでは独自のカスタムタスクを作成できます。これがまさにこれからやろうとしていることです。

最初にすべきことは、lib/ の中に mix/tasks/ ディレクトリを作ることです。ここにアプリケーション固有のMixタスクを作成します。

$ mkdir -p lib/mix/tasks/

そのディレクトリの中に、次のような hello.greeting.ex という新しいファイルを作成してみましょう。

defmodule Mix.Tasks.Hello.Greeting do
  use Mix.Task

  @shortdoc "Sends a greeting to us from Hello Phoenix"

  @moduledoc """
  This is where we would put any long form documentation and doctests.
  """

  @impl Mix.Task
  def run(_args) do
    Mix.shell().info("Greetings from the Hello Phoenix Application!")
  end

  # We can define other functions as needed here.
end

ここでは、作成中のMixタスクに含まれる動的な部分を簡単に見てみましょう。

最初にすべきことは、モジュールの名前を付けることです。すべてのタスクは Mix.Tasks 名前空間で定義する必要があります。これを mix hello.greeting として呼び出したいので、モジュール名を Hello.Greeting と命名します。

use Mix.Task 行は、このモジュールをMixタスクとして動作させるMixの機能をもたらします。

モジュール属性 @shortdoc は、ユーザーが mix help を起動したときのタスクを説明する文字列を保持します。

moduledoc は他のモジュールと同じ機能を果たします。長文のドキュメントやdoctestがあれば、ここに置くことができます。

関数 run/1 はMixタスクの重要な心臓部です。これは、ユーザーがタスクを起動したときにすべての作業を行う関数です。我々のタスクでは、アプリから挨拶を送るだけですが、run/1 関数を実装することで、必要なことを何でも行うことができます。Mix.shell().info/1 は、ユーザーへテキストを出力するのに好ましい方法であることに注目してください。

もちろん、このタスクは単なるモジュールなので、run/1 関数をサポートするために必要に応じて他のプライベート関数を定義できます。

タスクモジュールを定義したので、次のステップはアプリケーションをコンパイルすることです。

$ mix compile
Compiled lib/tasks/hello.greeting.ex
Generated hello.app

これで新しいタスクが mix help に表示されるようになりました。

$ mix help --search hello
mix hello.greeting # Sends a greeting to us from Hello Phoenix

mix help@shortdoc に入力したテキストとタスク名を表示していることに注目してください。

ここまでは順調ですが、うまくいくでしょうか?

$ mix hello.greeting
Greetings from the Hello Phoenix Application!

確かに動作しました。

新しいMixタスクにアプリケーションのインフラを使用させたい場合は、Mixタスクの実行時にアプリケーションが起動していることを確認し、設定する必要があります。これはとくに、Mixタスクの中からデータベースにアクセスする必要がある場合に便利です。ありがたいことに、Mixは @requirements モジュール属性を使って簡単に設定できるようにしてくれています。

  @requirements ["app.config"]

  @impl Mix.Task
  def run(_args) do
    Mix.shell().info("Now I have access to Repo and other goodies!")
    Mix.shell().info("Greetings from the Hello Phoenix Application!")
  end