查看源代码 混合任务

在新建的应用程序中,目前有许多内置的 Phoenix 特定和 Ecto 特定 混合任务 可供我们使用。我们也可以创建自己的应用程序特定任务。

注意要了解更多关于 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

我们在指南中已经看到过所有这些任务,但是将所有关于它们的信息集中在一起似乎是一个好主意。

我们将涵盖所有 Phoenix 混合任务,除了 phx.newphx.new.ectophx.new.web,它们是 Phoenix 安装程序的一部分。你可以通过调用 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/controllers/post_html/edit.html.heex
* creating lib/hello_web/controllers/post_html/post_form.html.heex
* creating lib/hello_web/controllers/post_html/index.html.heex
* creating lib/hello_web/controllers/post_html/new.html.heex
* creating lib/hello_web/controllers/post_html/show.html.heex
* creating lib/hello_web/controllers/post_html.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 迁移。

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: no route path for HelloWeb.Router matches \"/posts\"
  lib/hello_web/controllers/post_controller.ex:22: HelloWeb.PostController.index/2

如果我们不想为我们的资源创建一个上下文或模式,我们可以使用 --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/controllers/post_html/edit.html.heex
* creating lib/hello_web/controllers/post_html/post_form.html.heex
* creating lib/hello_web/controllers/post_html/index.html.heex
* creating lib/hello_web/controllers/post_html/new.html.heex
* creating lib/hello_web/controllers/post_html/show.html.heex
* creating lib/hello_web/controllers/post_html.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/controllers/post_html/edit.html.heex
* creating lib/hello_web/controllers/post_html/post_form.html.heex
* creating lib/hello_web/controllers/post_html/index.html.heex
* creating lib/hello_web/controllers/post_html/new.html.heex
* creating lib/hello_web/controllers/post_html/show.html.heex
* creating lib/hello_web/controllers/post_html.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/controllers/post_json.ex
* creating test/hello_web/controllers/post_controller_test.exs
* creating lib/hello_web/controllers/changeset_json.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 迁移。

Add the resource to the "/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: no route path for HelloWeb.Router matches \"/posts\"
  lib/hello_web/controllers/post_controller.ex:22: HelloWeb.PostController.index/2

mix phx.gen.json 也支持 --no-context--no-schema 和其他参数,就像在 mix phx.gen.html 中一样。

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/controllers/user_confirmation_html.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/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/controllers/user_registration_html.ex
* creating lib/hello_web/controllers/user_reset_password_html.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/controllers/user_session_html.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/controllers/user_settings_html.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.channelmix 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 生成器会告诉你在套接字中手动添加它。

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

此任务只有一个目的,就是向我们展示为给定路由器定义的所有路由。我们在 路由指南 中看到了它的广泛使用。

如果我们没有为该任务指定路由器,它将默认使用 Phoenix 为我们生成的路由器。

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

如果我们的应用程序有多个路由器,我们也可以指定一个单独的路由器。

$ mix phx.routes TaskTesterWeb.Router
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 中运行混合任务,iex -S mix phx.server

$ 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

此任务执行两个操作,它为我们的静态资产创建摘要,然后压缩它们。

"摘要" 在这里指的是资产内容的 MD5 摘要,它将被添加到该资产的文件名中。这为它创建了一种指纹。如果摘要没有改变,浏览器和 CDN 将使用缓存的版本。如果它确实发生了改变,它们将重新获取新版本。

在我们运行此任务之前,让我们检查 hello 应用程序中两个目录的内容。

首先是 priv/static/,它应该看起来类似于这样

├── assets
│   ├── app.css
│   └── app.js
├── favicon.ico
└── robots.txt

然后是 assets/,它应该看起来类似于这样

├── css
│   └── app.css
├── js
│   └── app.js
├── tailwind.config.js
└── vendor
    └── topbar.js

所有这些文件都是我们的静态资产。现在让我们运行 mix phx.digest 任务。

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

我们现在可以按照任务的建议检查 priv/static/ 目录的内容。我们会看到,来自 assets/ 的所有文件都已复制到 priv/static/ 中,并且每个文件现在都有两个版本。这些版本是

  • 原始文件
  • 一个使用 gzip 压缩的文件
  • 一个包含原始文件名及其摘要的文件
  • 一个包含文件名及其摘要的压缩文件

我们可以选择使用配置文件中的 :gzippable_exts 选项来确定哪些文件应该被 gzip 压缩。

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 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

我们可以通过在 psql 控制台中创建具有登录和创建数据库所需权限的“postgres”角色来解决此问题。

=# 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,否则会使用我们的应用程序生成的仓库)。它不会提示我们确认是否确定要删除数据库,因此请谨慎操作。

$ 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,
  username: "user",
  password: "pass",
  hostname: "localhost",
  database: "hello_repo",
. . .

当然,我们需要更改登录凭据以匹配我们的数据库期望的凭据。我们还需要更改其他环境的配置。

我们当然应该遵循说明并将我们的新仓库添加到我们的监督树中。在我们的 Hello 应用程序中,我们将打开 lib/hello/application.ex,并将我们的仓库作为工作程序添加到 children 列表中。

. . .
children = [
  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

请注意,迁移的文件名以创建文件时日期和时间的字符串表示形式开头。

让我们看一下 ecto.gen.migration 为我们在 priv/repo/migrations/20150318001628_add_comments_table.exs 中生成的文件。

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

  def change do
  end
end

请注意,只有一个函数 change/0,它将同时处理向前迁移和回滚。我们将使用 Ecto 方便的 DSL 定义我们想要的模式更改,Ecto 将根据我们是在向前迁移还是回滚来确定要执行的操作。非常棒。

我们要做的是创建一个 comments 表,其中包含一个 body 列、一个 word_count 列以及 inserted_atupdated_at 的时间戳列。

. . .
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.rollback 将处理与 ecto.migrate 相同的选项,因此 -n--step-v--to 将像 ecto.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 在任何模块中都具有相同的用途。我们可以在这里放置长格式文档和 doctests(如果需要)。

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.start"]

  @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