ElixirとPhoenixでCRUDなWebアプリケーションを作る

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Phoenixは生産性、信頼性、速度に強みを持つElixir製のWebアプリケーションフレームワークです。 前回は環境準備とPhoenixアプリケーションを作成するところまで書きました。

高生産性、高信頼性、高速のElixir製Webアプリケーションフレームワーク、Phoenixを始める

その続きで、今回はCRUDなWebアプリケーションを作成したいと思います。

プロジェクト作成

以下のコマンドでプロジェクトを作成します。

$ mix phoenix.new hello

プロジェクトができたら、前回の記事を元にプロジェクトの初期化を行ってください。

Modelの定義

PhoenixではDatabase操作にEctoというモジュールを使います(RailsのActiveRecordに相当するものですが、Elixirは関数型言語のためORマッパーではありません)。
Ectoについては今回深く説明しませんが、特徴はデータベース操作やクエリの組み立てを関数合成のように出来るところや、Changesetsという機能でバリデーションやパラメータのキャストを行うところです。
それではユーザーのmodelクラスを定義しましょう

hello/web/models/user.ex

defmodule Hello.User do
  use Hello.Web, :model

  schema "users" do
    field :name, :string
    field :email, :string
    field :password, :string, virtual: true
    field :password_hash, :string

    timestamps
  end

  @required_fields ~w(name email)
  @optional_fields ~w()
end

DSLでスキーマを定義しています。このschemafieldはmacroで定義されていて、それぞれのフィールド名はテーブルの各カラム名と一致します。
コードでは書かれていないですが、テーブルの主キーであるidフィールドは自動で定義されます。
また、passwordフィールドにはvirtualの指定がありますが、このフィールドはDBには存在しません。
DBのpassword_hashフィールドにハッシュ化したパスワードを登録するので、そのためのパスワードを保持するフィールドです。

続いて、いま定義したUserクラスの情報を保存するUserテーブルのmigrationファイルを作成しましょう。
以下のコマンドで作ります。

$ mix ecto.gen.migration create_user

生成されたファイルにindexを追加します(create uniqu_index..のところです)。

hello/priv/repo/migrations/yyyyMMddHHmmss_create_user.exs

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

  def change do
    create table(:users) do
      add :name, :string
      add :email, :string, null: false
      add :password_hash, :string

      timestamps
    end

    create unique_index(:users, [:email])
  end
end

ファイルを作成したら、以下のコマンドでmigrationを実行しUserテーブルを作ります。

mix ecto.migrate

Controllerの定義

ルートの定義

まず、router.exにユーザー情報のルートを書きます。

hello/web/router.ex

scope "/", Hello do
  pipe_through :browser

  resources "/users", UserController
end

reourcesの定義をすると、index, show, edit, update, deleteのルートが定義されます。
試しにコマンドでルートの確認をしてみましょう。

$ mix phoenix.routes
  user_path  GET     /users                Hello.UserController :index
  user_path  GET     /users/:id/edit       Hello.UserController :edit
  user_path  GET     /users/new            Hello.UserController :new
  user_path  GET     /users/:id            Hello.UserController :show
  user_path  POST    /users                Hello.UserController :create
  user_path  PATCH   /users/:id            Hello.UserController :update
             PUT     /users/:id            Hello.UserController :update
  user_path  DELETE  /users/:id            Hello.UserController :delete

もちろん、resources "/users"ではなく、以下のように定義しても同じようにルート定義できます。

hello/web/router.ex

scope "/", Hello do
  pipe_through :browser

  get "/users", UserController, :index
  get "/users/:id", Usercontroller, :show
  # その他のメソッドについても同様に定義する
end

indexメソッド

それではcontrollerクラスを作成します。
まず、indexメソッドです。

hello/web/controllers/user_controller.ex

defmodule Hello.UserController do
  use Hello.Web, :controller
  alias Hello.User

  def index(conn, _params) do
    users = Repo.all(User)
    render conn, "index.html", users: users
  end
end

Repo.all(User)でDBにあるユーザー一覧を取得してビューをレンダリングしています。
alias Hello.UserHello.UserモジュールをUserだけで呼び出せるようにするために記述しています。

次にビューファイルを作成しましょう。
Phoenixでは一つの画面でviewtemplateの二つのファイルを作ります。
それぞれ以下の役割を持っています。

  • viewはデータを表示用に加工するための関数を定義するモジュール
  • templateはHTMLもしくはJSONに変換されるファイル

templateファイルが従来のWeb Application Frameworkのviewファイルに近いですね。
viewファイルはとりあえず以下のように空のまま定義します。

hello/web/view/user_view.ex

defmodule Hello.UserView do
  use Hello.Web, :view
end

templateファイルも作成しましょう。

hello/web/templates/user/index.html.eex

<div class="row">
  <aside class="col-md-4">
    <%= for user <- @users do %>
    <section>
      <%= render "user.html", user: user, conn: @conn %>
      <%= link "Show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs" %>
      <%= link "Edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs" %>
    </section>
    <% end %>
  </aside>
</div>

hello/web/templates/user/user.html.eex

<a href="<%= user_path(@conn, :show, @user) %>">
<img src="<%= get_gravatar_url(@user) %>" class="gravatar">
</a>
<h1><%= @user.name %></h1>

(get_gravatar_urlはユーザーのavator画像を表示するための関数です、後ほど説明します)

newメソッド

新規ユーザーを作成するnewメソッドを追加します。

hello/web/controller/user_controller.ex

defmodule Hello.UserController do
  # 省略
  # ここを追加
  def new(conn, _params) do
    changeset = User.changeset(%User{})
    render conn, "new.html", changeset: changeset
  end
end

User.changeset(%{User{})はこれからUserクラスに作る関数です。
パラメータを受け取って、パラメータのキャスト、バリデーション、レコードの変更を行い、処理結果を保持しているEcto.Changesetを返却するようにします。ここではユーザーの作成に必要なフォーム情報を作成し返却しています。
それではUserモデルにchangeset関数を追加します

hello/web/models/user.ex

defmodule Hello.User do
  # 省略
  # ここを追加
  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> validate_length(:name, min: 1, max: 20)
    |> validate_format(:email, ~r/@/)
    |> unique_constraint(:email)
  end
end

ここではパラメータを必須フィールド、任意フィールドに割り当て、それぞれのフィールドのバリデーションを実行しています。
Railsの場合、各フィールドのバリデーションをフィールドの定義と同時に行っているのでここはPhoenixとRailsの違いが明確に出ています。

templateファイルも作成します

hello/web/templates/user/new.html.eex

<h1>New User</h1>

<%= form_for @changeset, user_path(@conn, :create), fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
  <p>エラーです、以下のメッセージを確認してください</p>
</div>
<% end %>

<div class="form-group">
  <%= text_input f, :name, placeholder: "Name", class: "form-control" %>
  <%= error_tag f, :name %>
</div>
<div class="form-group">
  <%= text_input f, :email, placeholder: "Email", class: "form-control" %>
  <%= error_tag f, :email %>
</div>
<div class="form-group">
  <%= password_input f, :password, placeholder: "Password", class: "form-control" %>
  <%= error_tag f, :password %>
</div>
<%= submit "Create User", class: "btn btn-primary" %>
<% end %>

createメソッド

ユーザー作成画面から呼ばれるcreateメソッドを作成します。
ユーザーのレコードをinsertし、成功したらユーザー一覧画面に遷移させます。

hello/web/controllers/user_controller.ex

defmodule Hello.UserController do
  # 省略
  # ここから追加
  def create(conn, %{"user" => user_params}) do
    changeset = User.changeset(%User{}, user_params)
    case Repo.insert(changeset) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "#{user.name}を作成しました")
        |> redirect(to: user_path(conn, :index))

      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
end

show

ユーザー詳細画面と画面を呼び出すshowメソッドを追加します。

hello/web/controllers/user_controller.ex

defmodule Hello.UserController do
  # 省略
  # ここから追加
  def show(conn, %{"id" => id}) do
    user = Repo.get(User, id)
    render conn, "show.html", user: user
  end
end

hello/web/templates/user/show.html.eex

<div class="row">
  <aside class="col-md-4">
    <section>
      <%= render "user.html", user: @user, conn: @conn %>
      <%= link "Edit", to: user_path(@conn, :edit, @user), class: "btn btn-default btn-xs" %>
      <%= button "Delete", to: user_path(@conn, :delete, @user),
      method: :delete,
      onclick: "return confirm(\"本当に削除しますか?\");",
      class: "btn btn-danger btn-xs" %>
      <%= link "Back", to: user_path(@conn, :index), class: "btn btn-default btn-xs"%>
    </section>
  </aside>
 </div>

edit, update

ユーザー更新のメソッドと更新画面を作成します

hello/web/controllers/user_controller.ex

defmodule Hello.UserController do
  # 省略
  # ここから追加
  def edit(conn, %{"id" => id}) do
    user = Repo.get(User, id)
    changeset = User.changeset(user)
    render(conn, "edit.html", user: user, changeset: changeset)
  end

  def update(conn, %{"id" => id, "user" => user_params}) do
    user = Repo.get(User, id)
    changeset = User.changeset(user, user_params)

    case Repo.update(changeset) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "更新しました")
        |> redirect(to: user_path(conn, :show, user.id))
      {:error, changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset)
    end
  end
end

templateファイルも用意します。フォームを別ファイルに切り出して共通化します。

hello/web/templates/user/edit.html.eex

<%= render "form.html", changeset: @changeset, action: user_path(@conn, :update, @user) %>

<%= link "Back", to: user_path(@conn, :index) , class: "btn btn-default btn-xs" %>

hello/web/templates/user/form.html.eex

<%= form_for @changeset, @action, fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>error! Please check the errors below.</p>
    </div>
  <% end %>

  <div class="form-group">
    <%= label f, :name, class: "control-label" %>
    <%= text_input f, :name, class: "form-control" %>
    <%= error_tag f, :name %>
  </div>

  <div class="form-group">
    <%= label f, :email, class: "control-label" %>
    <%= text_input f, :email, class: "form-control" %>
    <%= error_tag f, :email %>
  </div>

  <div class="form-group">
    <%= submit "Submit", class: "btn btn-primary" %>
  </div>
<% end %>

delete

最後にユーザー削除のメソッドを追加します。

hello/web/controllers/user_controller.ex

defmodule Hello.UserController do
  # 省略
  # ここから追加
  def delete(conn, %{"id" => id}) do
    user = Repo.get(User, id)
    Repo.delete(user)

    conn
    |> put_flash(:info, "削除しました")
    |> redirect(to: user_path(conn, :index))
  end
end

認証、認可

このままですと、認証していない状態でも各操作ができてしますので認証の仕組みを作ります。
comeoninというライブラリーを利用して簡単なパスワード認証機能を実装します。
comeonin

パスワード認証

mix.exsファイルにcomeoninを追加します。

hello/mix.exs

defmodule Hello.Mixfile do
  use Mix.Project
  
  # 省略
  
  # applications:に:commeoninを追加する
  def application do
    [mod: {Hello, []},
     applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :postgrex, :comeonin]]
  end
  
  # 省略
  
  # 依存ライブラリにcomeoninとそのバージョンを指定します
  defp deps do
    [{:phoenix, "~> 1.1.4"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_ecto, "~> 2.0"},
     {:phoenix_html, "~> 2.4"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.9"},
     {:cowboy, "~> 1.0"},	
     {:comeonin, "~> 2.0"}]
  end
  
  # 省略
end

以下のコマンドでライブラリをダウンロードします

mix deps.get

続いてUserクラスにユーザー登録時のパスワードチェック、ハッシュ化処理を追加します

hello/web/models/user.ex

defmodule Hello.User do
  # 省略
  # ここから追加
  def registration_changeset(model, params) do
    model
    |> changeset(params)
    |> cast(params, ~w(password), [])
    |> validate_length(:password, min: 6, max: 100)
    |> put_pass_hash()
  end

  defp put_pass_hash(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
        put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(pass))

      _ ->
        changeset
    end
  end
end

定義済みのchangeset(params)でパスワード以外のパラメータのバリデーションを行います。
そのあとにパスワードのバリデーション(文字列長チェック)を行い最後にput_pass_hash()でパスワードをハッシュ化してDBに保存しています。

controllerのcreateメソッドを、registration_changesetメソッドを使用するように修正します。

hello/web/controllers/user_controller.ex

defmodule Hello.UserController do
  # 省略	
  def create(conn, %{"user" => user_params}) do
    # ここをregistration_changesetに変更
    changeset = User.registration_changeset(%User{}, user_params)
    case Repo.insert(changeset) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "#{user.name}を作成しました")
        |> redirect(to: user_path(conn, :index))

      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
end

認証、認可モジュール

認証、認可用のモジュールを作り複数のコントローラから使用できるようにします。
また、ログイン、ログアウトのメソッドもこのモジュールに記述しましょう。
initcallメソッドはPlugとして必ず必要なメソッドです。詳細は今回説明しませんが、initで取得したconn(Plug.Conn構造体)をcallで受け取りDB接続処理を行っています。

hello/web/controllers/auth.ex

defmodule Hello.Auth do
  import Plug.Conn
  import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
  import Phoenix.Controller
  alias Hello.Router.Helpers
  alias Hello.User

  def init(opts) do
    Keyword.fetch!(opts, :repo)
  end

  def call(conn, repo) do
    user_id = get_session(conn, :user_id)
    user = user_id && repo.get(User, user_id)
    assign(conn, :current_user, user)
  end
  
  def login_by_name_and_pass(conn, name, given_pass, opts) do
    repo = Keyword.fetch!(opts, :repo)
    user = repo.get_by(User, name: name)

    cond do
      user && checkpw(given_pass, user.password_hash) ->
        # ここでpasswordのチェックが成功した場合にloginメソッドを呼び出してセッションにユーザーのidを保存しています(処理はloginメソッドに委譲しています)        
        {:ok, login(conn, user)} 
      user ->
        {:error, :unauthorized, conn}
      true ->
        dummy_checkpw()
        {:error, :not_found, conn}
    end
  end
  
  def login(conn, user) do
    conn
    |> assign(:current_user, user)
    |> put_session(:user_id, user.id)
    |> configure_session(renew: true)
  end

  def logout(conn) do
    configure_session(conn, drop: true)
  end  
  
  # 認可用メソッド
  def authenticate_user(conn, _opts) do
    if conn.assigns.current_user do
      conn
    else
      conn
      |> put_flash(:error, "ログインしてください")
      |> redirect(to: Helpers.session_path(conn, :new))
      |> halt()
    end
  end
end

コントローラ、ルーターで共通してauthenticate_userメソッドを使用できるようにweb.exにも追加します
hello/web/web.ex

defmodule Hello.Web do
  # 省略
  def controller do
    quote do
      use Phoenix.Controller

      alias Hello.Repo
      import Ecto
      import Ecto.Query, only: [from: 1, from: 2]

      import Hello.Router.Helpers
      import Hello.Gettext
      import Hello.Auth, only: [authenticate_user: 2] # ここを追加
    end
  end
  
  # 省略
  
  def router do
    quote do
      use Phoenix.Router

      import Hello.Auth, only: [authenticate_user: 2]
    end
  end
end

認可処理がリクエスト時に呼ばれるようにrouterを変更します。
pipeline :browserresouces "/users"の中に今回作ったモジュールと、認可用メソッドのauthenticae_userを追加します。

hello/web/router.ex

defmodule Hello.Router do
  use Hello.Web, :router
  
  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug Hello.Auth, repo: Hello.Repo # ここを追加
  end  
  
  scope "/", Hello do
    pipe_through :browser
    get "/", PageController, :index
    
    resources "/users", UserController do
      pipe_through [:authenticate_user] # ここを追加
    end
  end
end

続いて、コントローラの各メソッドで認可処理が走るように記述を追加します。

hello/web/controllers/user_controller.ex

defmodule Hello.UserController do
  use Hello.Web, :controller
  alias Hello.User
  plug :authenticate_user when action in [:index, :show, :edit, :update, :delete] # ここを追加
  
  # 省略
end

ログイン、ログアウト

SessionControllerという名称でlogin, logout処理を実装したコントローラを作成します。 まずはrouterに追加します。

hello/web/router.ex

defmodule Hello.Router do
  use Hello.Web, :router
  
  # 省略
  
  scope "/", Hello do
    pipe_through :browser
    get "/", PageController, :index
    
    resources "/users", UserController do
      pipe_through [:authenticate_user]
    end
    
    resources "/sessions", SessionController, only: [:new, :create, :delete]
  end
end

次にコントローラを作成します

hello/web/controllers/session_controller.ex

defmodule Hello.SessionController do
  use Hello.Web, :controller
  
  def new(conn, _) do
    render conn, "new.html"
  end

  def create(conn, %{"session" => %{"name" => user, "password" => pass}}) do
    case Hello.Auth.login_by_name_and_pass(conn, user, pass, repo: Repo) do
      {:ok, conn} ->
        conn
        |> put_flash(:info, "Welcome")
        |> redirect(to: page_path(conn, :index))

      {:error, _reason, conn} ->
        conn
        |> put_flash(:error, "ユーザー名/パスワードが不正です")
        |> render("new.html")
    end
  end

  def delete(conn, _) do
    conn
    |> Hello.Auth.logout()
    |> redirect(to: page_path(conn, :index))
  end
end

ログイン画面用のviewとtemplateファイルを作成します。

hello/web/views/session_view.ex

defmodule User.SessionView do
  use User.Web, :view
end

hello/web/templates/session/new.html.eex

<h1>Login</h1>

<%= form_for @conn, session_path(@conn, :create), [as: :session], fn f -> %>
<div class="form-group">
  <%= text_input f, :name, placeholder: "Name", class: "form-control" %>
</div>
<div class="form-group">
  <%= password_input f, :password, placeholder: "Password", class: "form-control" %>
</div>
<%= submit "Log in", class: "btn btn-primary" %>
<% end %>

アプリケーショントップ画面にユーザー登録画面とログイン画面へのリンクを追加しましょう。
ユーザーがセッションに存在する場合はログアウト、存在しない場合はユーザー登録、もしくはログイン画面へのリンクを表示しています。

hello/web/templates/layout/app.html.eex

<!-- 省略 -->
  <body>
    <div class="container">
      <div class="header">
        <ol class="breadcrumb text-right">
          <%= if @current_user do %>
          <li><%= @current_user.name %></li>
          <li>
            <%= link "Log out", to: session_path(@conn, :delete, @current_user), method: "delete" %>
          </li>
          <% else %>
          <li><%= link "Register", to: user_path(@conn, :new) %></li>
          <li><%= link "Log in", to: session_path(@conn, :new) %></li>
          <% end %>
        </ol>
        <span class="logo"></span>
      </div>

<!-- 省略 -->

その他微調整

avator画像

ユーザー画面が文字列だけだと寂しいのでGravatorに登録してあるイメージファイルを表示するようにします。
まずGravatorのAPIを叩くモジュールを作成します。
APIの詳細については説明を省きますが、emailのMD5値をパラメータとして渡すのでMD5暗号化する関数を作成しています。

hello/lib/Hello/gravator.ex

defmodule Hello.Gravatar do
  def get_gravatar_url(email) do
    gravatar_id = email_to_gravator_id(email)
    "https://secure.gravatar.com/avatar/#{gravatar_id}?s=50"
  end

  defp email_to_gravator_id(email) do
    email
    |> email_downcase
    |> email_crypt_md5
  end

  defp email_crypt_md5(email) do
    :erlang.md5(email)
    |> :erlang.bitstring_to_list
    |> Enum.map(&(:io_lib.format("~2.16.0b", [&1])))
    |> List.flatten
    |> :erlang.list_to_bitstring
  end

  defp email_downcase(email) do
    String.downcase(email)
  end
end

作成した関数を呼び出すようにUserのviewファイルを変更します

hello/web/views/user_view.ex

defmodule Hello.UserView do
  use Hello.Web, :view
  alias Hello.{User, Gravatar}

  def get_gravatar_url(%User{email: email}) do
    Gravatar.get_gravatar_url(email)
  end
end

CSS

cssで画面レイアウトの微調整をします。 以下のcssファイル作成します。

hello/priv/static/css/custom.css

html {
    overflow-y: scroll;
}

body {
    padding-top: 60px;
}

section {
    overflow: auto;
}

textarea {
    resize: vertical;
}

.center {
    text-align: center;
}

.center h1 {
    margin-bottom: 10px;
}

h1, h2, h3, h4, h5, h6 {
    line-height: 1;
}

h1 {
    font-size: 3em;
    letter-spacing: -2px;
    margin-bottom: 30ps;
    text-align: left;
}

h2 {
    font-size: 1.2em;
    letter-spacing: -1px;
    margin-bottom: 30px;
    text-align: center;
    font-weight: normal;
    color: #777777;
}

p {
    font-size: 1.1em;
    line-height: 1.7em;
}

.gravatar {
    float: left;
    margin-right: 10px;
}

aside section {
    padding: 10px 0;
    border-top: 1px solid #eeeeee;
}

aside section:first-child {
    border: 0;
    padding-top: 0;
}

aside section span {
    display: block;
    margin-bottom: 3px;
    line-height: 1;
}

aside section h1 {
    font-size: 1.4em;
    text-align: left;
    letter-spacing: -1px;
    margin-bottom: 3px;
    margin-top: 0px;
}

.content {
    display: block;
}

.timestamp {
    color: #777777;
}

aside textarea {
    height: 100px;
    margin-bottom: 5px;
}

templateファイルでこのファイルを読み込むように変更します。

hello/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>Hello Phoenix!</title>
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
    <!-- 以下の行を追加してください -->
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/custom.css") %>">
  </head>

  <body>
    <div class="container">
      <div class="header">
        <ol class="breadcrumb text-right">
          <%= if @current_user do %>
          <li><%= @current_user.name %></li>
          <li><%= link "Register", to: user_path(@conn, :new) %></li>
          <li> <%= link "Log out", to: session_path(@conn, :delete, @current_user), method: "delete" %> </li>
          <% else %>
          <li><%= link "Register", to: user_path(@conn, :new) %></li>
          <li><%= link "Log in", to: session_path(@conn, :new) %></li>
          <% end %>
        </ol>
        <span class="logo"></span>
      </div>

      <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>
    <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
  </body>
</html>

画面の確認

それではここまでに作成した画面を表示してみましょう!

トップ画面
phoenix-crud-2

ログイン画面
phoenix-crud-1

ユーザー一覧画面
phoenix-crud-7

ユーザー詳細画面
phoenix-crud-5

ユーザー更新画面
phoenix-crud-6

ユーザー新規作成画面
phoenix-crud-4

ログインしないでアクセスした場合
phoenix-crud-3

まとめ

ユーザー情報のCRUD操作を実装してきました。Ruby on RailsのようなMVC Frameworkをオブジェクト指向言語ではなく関数型言語でも実現しているところがPhoenixの面白いところですね。
また、ここまで触ってみた感じではかなりRuby on Railsの開発スタイルに近いと感じました。Railsのような開発生産性とElixirによる並列処理性能の両方を実現したFrameworkというのは、他の言語でもなかなかなく非常に可能性を感じます。
まだ、関連を持ったリソースの複雑な操作やPhoenixプロジェクトのディレクトリ構成については説明していないので、次回以降書いていきたいと思います。

参考資料

Programming Elixir
Programming Phoenix