檢視原始碼 混合任務

目前有許多內建的 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

它會告訴我們需要在 router 檔案中新增一行,但由於我們略過了 schema,它不會提到任何有關 ecto.migrate 的內容。

mix phx.gen.json

Phoenix 也提供產生所有代碼以建立完整 JSON 資源的能力,例如 Ecto 移轉、Ecto schema、包含所有必要動作和檢視的控制器。此指令不會為 app 建立任何版型。

mix phx.gen.json 任務會取得以下參數:context 的模組名稱、schema 的模組名稱、資源名稱和 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 完成檔案建立後,它會貼心地告訴我們需要在 router 檔案新增一行,以及執行我們的 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 資源,只需要一個 context,則可以使用 mix phx.gen.context 任務。它會產生一個 context、一個 schema、一個移轉和一個測試案例。

mix phx.gen.context 任務會取得以下參數:context 的模組名稱、schema 的模組名稱、資源名稱和 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 資源,而且不感興趣於產生或修改 context,則可以使用 mix phx.gen.schema 任務。它會產生一個 schema 和一個移轉。

mix phx.gen.schema 任務會取得以下參數: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 authentication 中更詳盡的逐步解說指南。

mix phx.gen.channelmix phx.gen.socket

這個任務會產生基礎的 Phoenix 通道、支援通路的 socket(如果你尚未建立),以及對它的測試案例。它僅接受通路的模組名稱為參數。

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

確認後,會建立一個通道,接著你需要連接你的終端機連接 socket。

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 建立 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 中執行 Mix 任務,如下所示,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 相依套件(如果不用附帶 --no-ecto 旗標執行 mix phx.new)。使用這些相依套件時,可以使用 Mix 任務處理常見的 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 任務來建立它們。讓我們逐步建立一個新的 Comment 資料表的遷移。

我們只需呼叫任務,並且使用我們要的模組名稱的 snake_case 版本。最好使用說明我們要遷移如何運作的名稱。

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

請注意,遷移的檔名會以建立檔案的日期和時間的字串表示作為開頭。

讓我們看一下 ecto.gen.migrationpriv/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 有和在任何模組中一樣的功能。如果有的話,我們可以在這裡放入長篇文件及 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.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