PhoenixでのCRUD処理およびメール送信（mixによる開発環境の構築からExrmによるデプロイまで）

# はじめに

HaskellのYesod、ScalaのPlay2に続く第三弾として、今回はElixirのPhoenixにてCRUD処理およびメール送信を行うWebアプリケーションを作成してみます。

なお、今回のコードは下記で公開しています。
https://github.com/imahiro-t/phoenix-app.git

他の関数型言語のWebフレームワークでの実装はこちら
[Yesod 1.4でのCRUD処理およびメール送信（stackによる開発環境の構築からketerによるデプロイまで）](http://qiita.com/erin/items/55e0109e4be6b3257bef)
[Play 2.5でのCRUD処理およびメール送信（activatorによる開発環境の構築からketerによるデプロイまで）](http://qiita.com/erin/items/1a49e728b42473a9364f)

# 環境

Kona Linux UE 2.0（Ubuntu 16.04 LTS）（開発環境）
Amazon EC2‎（Ubuntu 14.04 LTS）（実行環境）
MySQL 5.7.15

# 作成するWebアプリケーションについて

携帯電話のカレンダー機能には、予定を登録すると、その開始日時の○分前にアラームをならす機能があるかと思います。私のガラホ（AQUOS K SHF32）ではアラームは呼出音の設定と連動しているようで、普段呼出音を切ってバイブのみで使用しているので、当然アラームは鳴らず、それだけならまだしも、バイブすらしてくれないという使い勝手の悪さ。

概要は、サイトにアクセスし、件名、日付、時間、通知（○分前）、説明を登録すると、サーバ側でその通知時刻になった時点で、メールを配信してくれるというWebアプリケーションです。

![notify_index.png](https://qiita-image-store.s3.amazonaws.com/0/94455/49ef8e08-d20a-e8ed-2c0b-db229da00df5.png)
![notify_edit.png](https://qiita-image-store.s3.amazonaws.com/0/94455/a23143ca-6b18-90a6-0886-8d5e272081b8.png)

# Elixir、Phoenixのインストール

まずは公式サイトを参考に開発環境にElixirをインストールします。

[Elixirの公式サイト](http://elixir-lang.org/install.html)

```
$ wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
$ sudo apt-get update
$ sudo apt-get install esl-erlang
$ sudo apt-get install elixir
```

次に公式サイトを参考に開発環境にPhoenixをインストールします。

[Phoenixの公式サイト](http://www.phoenixframework.org/docs/installation)

```
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
```

Elixirの「mix」は、Scalaの「activator」やHaskellの「stack」に相当します。

オプションで「Node.js (>= 5.0.0)」「Brunch」、さらにLinux環境では「inotify-tools」が必要とのことですので、それぞれの公式サイトを参考にインストールします。

[Node.jsの公式サイト](https://nodejs.org/en/download/)
[Brunchの公式サイト](http://brunch.io/)
[inotify-toolsの公式サイト](https://github.com/rvoicilas/inotify-tools/wiki)

# プロジェクトの作成

```
$ mix phoenix.new notify --database mysql
$ cd notify
```

`mix phoenix.new [プロジェクト名]`はPheonix用のテンプレートを作成するコマンドで、今回は「notify」というプロジェクト名で、データベースにMySQLを使ったPheonixアプリケーションのテンプレートを作成します。（Brunchをインストールしていない場合は、`--no-brunch`オプションをつけてプロジェクトを作成します。）

notifyディレクトリ内にテンプレートが作成されるので、後はnotifyディレクトリ内でファイルの追加、編集を行っていくことになります。

# Modelの作成

データベースとのやり取りは[Ecto](https://hexdocs.pm/ecto/Ecto.html)というORMのようなモデルとデータベースを紐付けを行うマッパーモジュールを使います。

プロジェクト作成時に`config/dev.exs`にデータベースとの接続のための設定が自動的に作られていますので、ユーザ名やパスワード等、設定内容を適宜変更してModelを作成します。

```config/dev.exs
          ・
          ・
config :notify, Notify.Repo,
  adapter: Ecto.Adapters.MySQL,
  username: "root",
  password: "",
  database: "notify_phoenix",
  pool_size: 20
```

```
$ mix ecto.create
$ mix phoenix.gen.model Notification notifications subject:string action_date:date action_time:time notify_before:integer summary:text notify_date_time:datetime sent:boolean
$ mix ecto.migrate
```

上記を実行することで、データベースにテーブルが作成されるだけでなく、`web/models/notification.ex`にフォームとのマッピングやバリデーションのスケルトンが実装されたデータモデルが作成されます。

今回のWebアプリでは、「notify_date_time」や「sent」はフォーム内で使用しないカラムとなり、「summary」は入力が任意のカラムとなりますので`web/models/notification.ex`内の`changeset`を一部修正します。

```web/models/notification.ex
defmodule Notify.Notification do
          ・
          ・
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:subject, :action_date, :action_time, :notify_before, :summary])
    |> validate_required([:subject, :action_date, :action_time, :notify_before])
  end
end
```

なお、`mix phoenix.gen.model ・・・`を`mix phoenix.gen.html ・・・`にすると、後述するControllerやView（テンプレート含む）に至るまで、すべてを自動生成してくれます。

# ルーティング

ルーティング（URLパターンからハンドラーへの対応付け）を設定します。

```web/routes.ex
defmodule Notify.Router do
          ・
          ・
  scope "/", Notify do
          ・
          ・
    resources "/notify", NotifyController, except: [:show]
  end
end
```

ルーティングの設定は`get "/notify", NotifyController, :index`や`post "/notify", NotifyController, :create`のように個別に設定していくことも可能ですが、今回は基本的なCRUD処理のみのため`resources "/notify", NotifyController, except: [:show]`で一括して設定しています。（今回のWebアプリでは使用しないので`except: [:show]`で`Notify.NotifyController.show`関数へのルーティングを外しています。）

# Controller（ハンドラー）の作成

Phoenixではルーティングの設定により、URLパターンから該当するハンドラーが呼び出されることになります。

`web/controllers/notify_controller.ex`に下記の実装を。

```web/controllers/notify_controller.ex
defmodule Notify.NotifyController do
  use Notify.Web, :controller

  alias Notify.Notification

  def index(conn, _params) do
    changeset = Notification.changeset(%Notification{})
    conn
    |> assign(:notifications_sent_not_yet, notifications_sent_not_yet)
    |> assign(:notifications_sent, notifications_sent)
    |> assign(:changeset, changeset)
    |> assign(:title, "登録画面")
    |> render("index.html")
  end

  def create(conn, %{"notification" => notification_params}) do
    changeset = Notification.changeset(%Notification{}, notification_params |> modify_action_time)

    case Repo.insert(changeset) do
      {:ok, notification} ->
        notify_date_time = calc_notify_date_time(notification)
        notification
        |> Ecto.Changeset.change(%{notify_date_time: notify_date_time})
        |> Repo.update!
        conn
        |> put_flash(:info, "[#{notification.subject}]を作成しました")
        |> redirect(to: notify_path(conn, :index))
      {:error, changeset} ->
        conn
        |> assign(:notifications_sent_not_yet, notifications_sent_not_yet)
        |> assign(:notifications_sent, notifications_sent)
        |> assign(:changeset, changeset)
        |> assign(:title, "登録画面")
        |> render("index.html")
    end
  end

  def edit(conn, %{"id" => id}) do
    notification = Repo.get!(Notification, id)
    changeset = Notification.changeset(notification)
    conn
    |> assign(:notification, notification)
    |> assign(:changeset, changeset)
    |> assign(:title, "編集画面")
    |> render("edit.html")
  end

  def update(conn, %{"id" => id, "notification" => notification_params}) do
    notification = Repo.get!(Notification, id)
    changeset = Notification.changeset(notification, notification_params |> modify_action_time)

    case Repo.update(changeset) do
      {:ok, notification} ->
        notify_date_time = calc_notify_date_time(notification)
        notification
        |> Ecto.Changeset.change(%{notify_date_time: notify_date_time, sent: false})
        |> Repo.update!
        conn
        |> put_flash(:info, "[#{notification.subject}]を更新しました")
        |> redirect(to: notify_path(conn, :index))
      {:error, changeset} ->
        conn
        |> assign(:notification, notification)
        |> assign(:changeset, changeset)
        |> assign(:title, "編集画面")
        |> render("edit.html")
    end
  end

  def delete(conn, %{"id" => id}) do
    notification = Repo.get!(Notification, id)

    Repo.delete!(notification)
    conn
    |> put_flash(:info, "[#{notification.subject}]を削除しました")
    |> redirect(to: notify_path(conn, :index))
  end

  defp modify_action_time(params) do
    r = ~r/^\d{2}:\d{2}$/
    %{params | "action_time" => Regex.replace(r, params["action_time"], "\\0" <> ":00")}
  end

  defp notifications_sent_not_yet do
    Notification
    |> where([u], u.sent == false)
    |> Repo.all
  end

  defp notifications_sent do
    Notification
    |> where([u], u.sent == true)
    |> Repo.all
  end

  defp calc_notify_date_time(notification) do
    Ecto.DateTime.from_date_and_time(notification.action_date, notification.action_time)
    |> add_minutes(-(notification.notify_before))
  end

  defp add_minutes(date_time, minutes) do
    date_time
    |> Ecto.DateTime.to_erl
    |> :calendar.datetime_to_gregorian_seconds
    |> Kernel.+(60 * minutes)
    |> :calendar.gregorian_seconds_to_datetime
    |> :calendar.local_time_to_universal_time_dst
    |> hd
    |> Ecto.DateTime.from_erl
  end
end

```

GETで/notifyが呼び出されたときは`index`が呼ばれ、データベースから通知予定のレコードリストと通知済みのレコードリストを取得し、登録画面を表示しています。

POSTで/notifyが呼び出されたときは`create`が呼ばれ、入力内容をデータベースに登録し、その後通知日時を計算してから再度レコードを更新し、登録画面にリダイレクトしています。

GETで/notify/:id/editが呼び出されたときは`edit`が呼ばれ、データベースからidに該当するレコードを取得し、編集画面を表示しています。

PUTで/notify/:idが呼び出されたときは、`update`が呼ばれ、入力内容でデータベースを更新し、その後通知日時を計算してから再度レコードを更新し、登録画面にリダイレクトしています。

DELETEで/notify/:idが呼び出されたときは、`delete`が呼ばれ、データベースからidに該当するレコードを削除し、登録画面にリダイレクトしています。

「PUT」や「DELETE」はHTMLのFORMではサポートされていませんが、フォーム内のHIDDENフィールドに"_method"属性をもたせることで内部的に実現しているようです。

レコードの登録、更新はEcto.Changesetを介して行うことになり、フォームからの入力内容もまずはEcto.Changesetに変換されることになります。

Ecto.Changesetは差分アップデートを行うため、Notify.Notification.changeset内でわざわざselect文を発行してフォームの入力内容とデータベースのレコードとの差分データを作成しているようです。そのため、action_date、action_time、notify_beforeからnotify_date_timeを計算する場合、先に登録・更新処理を行い、その後、登録・更新が完了したレコードに対して通知日時の計算を行って再度レコードの更新処理を行っています。

# Viewの作成

まずはデフォルトで用意されている共通点プレートを修正します。（jQueryを読みこんだり、タイトルロゴを消したり。）

```web/templates/layout/app.html.eex
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title><%= @title %></title>
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js"></script>
  </head>

  <body>
    <div class="container">
      <h2><%= @title %></h2>

      <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
      <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>

      <main role="main">
        <%= render @view_module, @view_template, assigns %>
      </main>

    </div> <!-- /container -->
    <script src="<%= static_path(@conn, "/js/app.js") %>"></script>
  </body>
</html>
```

次に登録画面と編集画面のテンプレートを読み込むViewを`web/views/notify_view.ex`に。

```web/views/notify_view.ex
defmodule Notify.NotifyView do
  use Notify.Web, :view
end
```

Phoenixでは[Pnoenix.HTML](https://hexdocs.pm/phoenix_html/Phoenix.HTML.html)を利用してテンプレートの記述を行うのですが、Pnoenix.HTMLで用意されているフォームに使用する日付型と日時型のコンポーネントが独自のコンポーネント（年・月・日がそれぞれコンボボックスになっているコンポーネント）になっているので、`<input type="date">`や`<input type="time">`が使用できるように別途ヘルパーモジュールを作成します。

```web/views/form_helpers.ex
defmodule Notify.FormHelpers do
  use Phoenix.HTML

  def date_input(form, field, opts) do
    input(:date, form, field, opts)
  end

  def time_input(form, field, opts) do
    input(:time, form, field, opts)
  end

  defp input(type, form, field, opts) do
    opts =
      opts
      |> Keyword.put_new(:type, type)
      |> Keyword.put_new(:id, field_id(form, field))
      |> Keyword.put_new(:name, field_name(form, field))
      |> Keyword.put_new(:value, field_value(form, field))
    tag(:input, opts)
  end
end
```

```web/web.ex
defmodule Notify.Web do
          ・
          ・
  def view do
    quote do
          ・
          ・
      import Notify.FormHelpers
    end
  end
          ・
          ・
end
```

登録画面と編集画面の共通フォームを`web/templates/notify/form.html.eex`に。

```web/templates/notify/form.html.eex
<%
    notify_before_options =
        [{"5分前","5"},{"10分前","10"},{"15分前","15"},{"20分前","20"}] ++
        [{"30分前","30"},{"45分前","45"},{"1時間前","60"},{"2時間前","120"}]
%>
<div class="form-group">
    <%= label @form, :subject, "件名", class: "control-label" %>
    <%= text_input @form, :subject, class: "form-control", required: "" %>
    <%= error_tag @form, :subject %>
</div>

<div class="form-group">
    <%= label @form, :action_date, "日付", class: "control-label" %>
    <%= date_input @form, :action_date, class: "form-control", required: "" %>
    <%= error_tag @form, :action_date %>
</div>

<div class="form-group">
    <%= label @form, :action_time, "時間", class: "control-label" %>
    <%= time_input @form, :action_time, class: "form-control", required: "" %>
    <%= error_tag @form, :action_time %>
</div>

<div class="form-group">
    <%= label @form, :notify_before, "通知", class: "control-label" %>
    <%= select @form, :notify_before, notify_before_options, class: "form-control", required: "" %>
    <%= error_tag @form, :notify_before %>
</div>

<div class="form-group">
    <%= label @form, :summary, "説明", class: "control-label" %>
    <%= textarea @form, :summary, class: "form-control" %>
    <%= error_tag @form, :summary %>
</div>
```

「日付」と「時間」フィールドについては、先ほど作成したヘルパーモジュールの関数を呼び出しています。

登録画面を`web/templates/notify/index.html.eex`に。

```web/templates/notify/index.html.eex
<table class="table">
    <thead>
    <tr>
        <th>通知リスト</th>
        <th></th>
    </tr>
    </thead>
    <tbody>
    <%= for notification <- @notifications_sent_not_yet do %>
    <tr>
        <td><%= notification.subject %></td>
        <td class="text-right">
            <%= link "編集", to: notify_path(@conn, :edit, notification), class: "btn btn-default btn-xs" %>
            <%= link "削除", to: notify_path(@conn, :delete, notification), method: :delete, data: [confirm: "削除してもいいですか?"], class: "btn btn-danger btn-xs" %>
        </td>
    </tr>
    <% end %>
    </tbody>
</table>

<%= form_for @changeset, notify_path(@conn, :create), fn form -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
    <p>入力に誤りがあります</p>
</div>
<% end %>

<%= render "form.html", changeset: @changeset, form: form %>

<div class="button-group">
    <%= submit "登録", class: "btn btn-primary create" %>
</div>

<% end %>

<table class="table">
    <thead>
    <tr>
        <th>通知済リスト</th>
        <th></th>
    </tr>
    </thead>
    <tbody>
    <%= for notification <- @notifications_sent do %>
    <tr>
        <td><%= notification.subject %></td>
        <td class="text-right">
            <%= link "編集", to: notify_path(@conn, :edit, notification), class: "btn btn-default btn-xs" %>
            <%= link "削除", to: notify_path(@conn, :delete, notification), method: :delete, data: [confirm: "削除してもいいですか?"], class: "btn btn-danger btn-xs" %>
        </td>
    </tr>
    <% end %>
    </tbody>
</table>
```

編集画面を`web/templates/notify/edit.html.eex`に。

```web/templates/notify/edit.html.eex
<%= form_for @changeset, notify_path(@conn, :update, @notification), fn form -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
    <p>入力に誤りがあります</p>
</div>
<% end %>

<%= render "form.html", changeset: @changeset, form: form %>

<div class="button-group">
    <%= submit "更新", class: "btn btn-primary update", id: "update_btn" %>
    <%= submit "削除", class: "btn delete", id: "delete_btn" %>
</div>

<% end %>

<%= link "HOME", to: notify_path(@conn, :index) %>
```

更新ボタン・削除ボタンが押された際に、隠しフィールドの"_method"の値を"put"と"delete"に切り替えるJavaScriptを`web/static/js/app.js`に。

```web/static/js/app.js
          ・
          ・
$(function(){
    $('#update_btn').click(function(){
        $('input[name="_method"]').val('put');
    });
    $('#delete_btn').click(function(e){
        if (window.confirm('削除してもいいですか?')) {
            $('input[name="_method"]').val('delete');
        } else {
            e.preventDefault();
        }
    });
});
```

最後にCSSを`web/static/css/app.css`に。

```web/static/css/app.css
h2 {
    background-color: #F36636;
    color: #FFFFFF;
    border-radius: 5px;
    padding: 5px;
    text-align: center;
    width: 280px;
}
.container, .alert, main {
    width: 280px;
}
.button-group {
    text-align: center;
}
button.create {
    width: 280px;
}
button.update, button.delete {
    width: 135px;
}
```

※Brunchをインストールしていない場合は、上記２つは`priv/static/js/app.js`と`priv/static/css/app.css`の下部に設定します。

# タイマーの作成

通知時刻にメール送信を行うため、1分間隔でデータベースを検索し、該当するレコードがある場合に、データベースの更新、メールの送信を行います。

まずはメール送信のためのモジュールを`mix.ex`に追加します。

```mix.ex
defmodule Notify.Mixfile do
          ・
          ・
  def application do
    [mod: {Notify, []},
     applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :mariaex, :bamboo, :bamboo_smtp]]
  end
          ・
          ・
  defp deps do
    [{:phoenix, "~> 1.2.1"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:mariaex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"},
     {:bamboo, "~> 0.7"},
     {:bamboo_smtp, "~> 1.2.1"}]
  end
          ・
          ・
end
```

メールモジュールをNotify.Mailerとして登録します。

```lib/notify/mailer.ex
defmodule Notify.Mailer do
  use Bamboo.Mailer, otp_app: :notify
end
```

メール送信用のコンフィグレーションを`config/dev.exs`と`config/config.exs`に。

```config/dev.exs
          ・
          ・
config :notify, Notify.Mailer,
  adapter: Bamboo.SMTPAdapter,
  server: "localhost",
  port: 25,
  username: "",
  password: "",
  tls: :never, # can be `:always` or `:never`
  ssl: false, # can be `true`
  retries: 1
```

```config/config.exs
          ・
          ・
# General application configuration
config :notify,
  ecto_repos: [Notify.Repo],
  mail_from: "NOTIFY SENDER <from@xxxxxx.xxx>",
  mail_to: "to@xxxxxx.xxx"
          ・
          ・
```

未通知かつ通知時刻のレコードを検索し、メール送信を行うモジュールを`web/controllers/notify_checker.ex`に。

```web/controllers/notify_checker.ex
defmodule Notify.NotifyChecker do
  import Ecto.Query

  alias Notify.Repo
  alias Notify.Notification
  alias Notify.Mailer
  alias Notify.Email

  def check_notify do
    for n <- notifications_to_send do
      send_mail n
      update_table n
    end
  end

  defp notifications_to_send do
    Notification
    |> where([u], u.sent == false and u.notify_date_time <= from_now(0, "microsecond"))
    |> Repo.all
  end

  defp send_mail(notification) do
    notification
    |> Email.notify_mail
    |> Mailer.deliver_now
  end

  defp update_table(notification) do
    notification
    |> Ecto.Changeset.change(%{sent: true})
    |> Repo.update!
  end
end

defmodule Notify.Email do
  import Bamboo.Email

  def notify_mail(notification) do
    new_email
    |> to(Application.get_env(:notify, :mail_to))
    |> from(Application.get_env(:notify, :mail_from))
    |> subject("【通知】 #{notification.subject}")
    |> text_body(notification |> notify_body)
  end

  defp notify_body(notification) do
    """
    [件名]
    #{notification.subject}
    [日付]
    #{notification.action_date}
    [時間]
    #{notification.action_time}
    [通知]
    #{Ecto.DateTime.utc |> to_local_date_time}
    [説明]
    #{notification.summary}
    """
  end

  defp to_local_date_time(utc_date_time) do
    utc_date_time
    |> Ecto.DateTime.to_erl
    |> :calendar.universal_time_to_local_time
    |> Ecto.DateTime.from_erl
  end
end
```

タイマーはGenServerにしてSupervisorが管理するようにします。

```lib/notify/timer.ex
defmodule Notify.Timer do
  use GenServer

  def start_link(name) do
    GenServer.start_link(__MODULE__, :ok, name: name)
  end

  def init(:ok) do
    check_forever
    {:ok, %{}}
  end

  def handle_info(:check_notify, _) do
    spawn(fn -> Notify.NotifyChecker.check_notify end)
    check_forever
    {:noreply, ""}
  end

  defp check_forever do
    {_, _, sec} = Ecto.Time.utc |> Ecto.Time.to_erl
    Process.send_after(self, :check_notify, (60-sec)*1000)
  end

end
```

```lib/notify.ex
defmodule Notify do
  use Application
  def start(_type, _args) do
    children = [
          ・
          ・
      worker(Notify.Timer, [Notify.Timer]),
    ]
          ・
          ・
  end
          ・
          ・
end
```

# 動作確認

```
$ mix deps.get
$ mix phoenix.server
```

`http://localhost:4000/notify`にアクセスして動作確認してみてください。

# Exrmによるデプロイ

最後に[Elixir Release Manager](https://github.com/bitwalker/exrm)（Exrm）を使ってAmazon EC2‎にデプロイしてみます。

まずはモジュールを`mix.ex`に追加します。

```mix.ex
defmodule Notify.Mixfile do
          ・
          ・
  defp deps do
    [{:phoenix, "~> 1.2.1"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:mariaex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"},
     {:bamboo, "~> 0.7"},
     {:bamboo_smtp, "~> 1.2.1"},
     {:exrm, "~> 1.0"}]
  end
          ・
          ・
end
```

ホスト名の設定を行います。

```config/prod.exs
          ・
          ・
config :notify, Notify.Endpoint,
  http: [port: {:system, "PORT"}],
  url: [host: "host.xxxx.xx"],
  cache_static_manifest: "priv/static/manifest.json",
  root: ".",
  server: true,
  version: Mix.Project.config[:version]
          ・
          ・
```

データベースアクセスとメール送信用のコンフィグレーションは`config/prod.secret.exs`に行います。

```config/prod.secret.exs
          ・
          ・
config :notify, Notify.Repo,
  adapter: Ecto.Adapters.MySQL,
  username: "root",
  password: "",
  database: "notify_phoenix",
  pool_size: 20

config :notify, Notify.Mailer,
  adapter: Bamboo.SMTPAdapter,
  server: "localhost",
  port: 25,
  username: "",
  password: "",
  tls: :never, # can be `:always` or `:never`
  ssl: false, # can be `true`
  retries: 1
```

デプロイ用のモジュールを作成します。

```
$ mix deps.get
$ MIX_ENV=prod mix phoenix.digest
$ MIX_ENV=prod mix compile
$ MIX_ENV=prod mix release
```

作成された`rel/notify/releases/0.0.1/notify.tar.gz`をサーバにアップロードし、予め作成しておいた`/opt/notify_phoenix/`内に放り込んで解凍します。

次に起動用のスクリプトを`/etc/init/notify_phoenix.conf`に。

```/etc/init/notify_phoenix.conf
description "notify_phoenix"

setuid ubuntu
setgid ubuntu

start on runlevel [2345]
stop on runlevel [016]

expect stop
respawn

env MIX_ENV=prod
export MIX_ENV

## Uncomment the following two lines if we configured
## our port with an environment variable.
env PORT=4444
export PORT

## Add app HOME directory.
env HOME=/opt/notify_phoenix
export HOME

pre-start exec /bin/sh $HOME/bin/notify start

post-stop exec /bin/sh $HOME/bin/notify stop
```

起動します。

```
$ sudo start notify_phoenix
```

`http://host.xxxx.xx:4444/notify`にアクセスして動作確認してみてください。


# さいごに

関数型言語のWebフレームワークとして、今回はElixirのPhoenixを試してみました。

Elixirのパイプラインでの記述方法がとても楽しく、できるだけパイプラインを使うよう心がけていたら、自然と宣言的な記述を持つ小さな関数がたくさん出来ました。

パイプラインはデータを変換していく過程がよく分かり、主軸でないものを外部関数として放り出すことで、関数の責務を限定的するという良い習慣が身につきそうです。

Phoenixも洗練されたWebフレームワークで、ElixirとWebアプリケーションの相性の良さを感じることができました。

異なる言語・異なるWebフレームワークでも、毎度似た思想を持つフレームワークで同じWebアプリを作っているので、実装の流れはだいたい同じようになりますが、今回のElixir＋Phoenixの開発効率は群を抜いていると感じました。

Elixirではそれぞれのモジュールが独立していて、相互がメッセージのやり取りで動いているらしいのですが、表面上の実装ではそれを感じることはできず、通常の関数呼び出しのように見えます。（関数が呼ばれると関数内部でメッセージパッシングを行っている感じだと思います。）

今回のWebアプリでも、EctoやMailer、Timerなどがそれぞれ独立したコンフィグレーションを持って動いているので、相互に依存関係が発生せず、例えば別プロセスで動いているTimerがデータベースにアクセスしたりメールを送信したりする際にも、関数型言語でありながら引数でデータベースの情報やコンフィグレーションの情報を渡す必要がなく、とてもすっきりとしたコードになっています。（かと言ってグローバルに値を持っているということでもなく、どちらかと言えばDIのような感じでしょうか。）

他の言語でもメッセージパッシングによる同様の実装は可能なのでしょうが、Elixirではデフォルトが「これ」というのが開発効率の面で大きいのだと思いました。