Phoenix LiveView についてまた取り上げます。
この機能は最近活発に開発が行われていまして、サーバーサードレンダリングの新しい潮流として期待が持てます。
今度はより具体的な例として、認証機能付きチャットアプリを作成します。
これから作成するアプリには、以下の要素が含まれています。
本テーマは7記事構成になっています。
7記事それぞれで取り扱う内容は以下です。
本テーマは以下の方針で記述します。
完成版の画面イメージは以下のようになっています。
まずはプロジェクトの準備を行います。
記事公開時点でのそれぞれのバージョンは以下です。
項目 | バージョン |
---|---|
erlang | 22.1.4 |
elixir | 1.9.2 |
Phoenix | 1.4.10 |
phoenixliveview | 0.3.1 |
本記事では、以下の手順にしたがって進めてゆきます。
lv_chat
というプロジェクトを作成します。
$ mix phx.new lv_chat
プロジェクトのディレクトリに移動し、DBを作成します。
$ cd lv_chat
$ mix ecto.create
プロジェクトに必要なパッケージを追加します。
以下のパッケージを追加します。
パッケージ | 用途 |
---|---|
phoenixliveview | LiveViewの機能実装に使用 |
pbkdf2 | パスワードのハッシュに使用 |
# mix.exs
defp deps do
[
...
{:phoenix_live_view, "~> 0.3.0"}, {:pbkdf2_elixir, "~> 1.0"}, ...
]
end
パッケージを取得します。
$ mix deps.get
LiveViewのセットアップを行います。
以前公開した以下のブログを参考にしてください。
「Phoenix LiveViewによる動的サーバーサイドレンダリング1」
ただし、LiveViewは現在も開発中なので、この手順が変わる可能性があります。
おかしいなと思ったら本家を参照してください。
アクセスするユーザーの区別を行えるようにするため、ユーザー登録機能を作成します。
ユーザー・アカウントは以下のように定義します。
メンバー | 型 | 備考 |
---|---|---|
name | string |
|
password | string |
|
password_hash | string |
|
上記定義をもとに、以下のコマンドでコンテキストを作成します。
$ mix phx.gen.context Accounts User users name:string password_hash:string password:string
ecto.migrate
を行う前に、自動生成されたファイルを意図したものになるように少々変更します。
変更点は以下です。
項目 | 変更点 |
---|---|
共通 |
|
:password |
|
:name |
|
上記変更を以下のように記述します。
# /lib/lv_chat/accounts/user.ex
defmodule LvChat.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :password, :string, virtual: true field :password_hash, :string
timestamps()
end
@doc false
def changeset(user, attrs) do
user
|> cast(attrs, [:name]) |> validate_required([:name]) |> validate_length(:name, min: 1, max: 20) |> unique_constraint(:name) end
end
マイグレーションファイルは、phx.gen.context
を行った際に自動的に生成されます。
場所は以下にあります。
priv/repo/migrations/*_create_users.ex
※ ファイル名の*には日付と時刻が入ります
生成されたマイグレーションファイルに対して以下の変更を行います。
生成された項目 | 変更点 |
---|---|
:password |
|
:name |
|
最終的に以下のようになります。
# /priv/repo/migrations/*_create_users.ex
defmodule Chat.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :name, :string, null: false add :password_hash, :string
timestamps()
end
create unique_index(:users, [:name])
end
end
必要な作業が完了したので、以下のコマンドによりマイグレーションを行います。
$ mix ecto.migrate
生成されたいくつかのファイルに実際の機能を追加していきます。
user.ex
に対して、さらにユーザー登録用関数を追加します。
その関数では、以下の事を行っています。
項目 | 変更点 |
---|---|
追加処理 |
|
:password |
|
上記変更を以下のように記述します。
# /lib/chat/accounts/user.ex
def registration_changeset(user, params) do
user
|> changeset(params)
|> cast(params, [:password])
|> validate_required([:password])
|> validate_length(:password, min: 3, max: 100)
|> put_pass_hash()
end
def put_pass_hash(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
put_change(changeset, :password_hash, Pbkdf2.hash_pwd_salt(pass))
_ ->
changeset
end
end
コントローラーから呼び出すための関数を追加します。
それぞれ create_user/1
と change_user/1
のユーザー登録版です。
# /lib/chat/accounts.ex
def register_user(attrs \\ %{}) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
def change_registration(%User{} = user, params) do
User.registration_changeset(user, params)
end
ユーザーが登録できるようになったので、開発の便宜上ダミーデータをDBに登録しておきます。
seeds.exs
にダミーデータを定義し、register_user/1
を呼び出してユーザー登録を行います。
# /priv/repo/seeds.exs
alias LvChat.Accounts
%{name: "freddie", password: "mercury"} |> Accounts.register_user
%{name: "brian", password: "may"} |> Accounts.register_user
%{name: "john", password: "deacon"} |> Accounts.register_user
%{name: "roger", password: "taylor"} |> Accounts.register_user
因みに、arg |> func
は func( arg )
とも書けます。
以下のコマンドを実行して、データを追加します。
$ mix run priv/repo/seeds.exs
これで4人のメンバーが揃いました。
ユーザー登録ページを作成する前に、そこで必要となるログイン処理の機能を作成します。
lib/lv_chat_web/controllers/
に auth.ex
というファイルを作成し、そこにログイン処理関連の実装を行っていきます。
# /lib/lv_chat_web/controllers/auth.ex
defmodule LvChatWeb.Auth do
import Plug.Conn
def login(conn, user) do
conn
|> put_current_user(user)
|> put_session(:user_id, user.id)
|> configure_session(renew: true)
end
def logout(conn) do
configure_session(conn, drop: true)
end
defp put_current_user(conn, user) do
conn
|> assign(:current_user, user)
end
end
以下の手順でユーザー登録機能を作っていきます。
とりあえず登録のところのみ作成します。
# /lib/lv_chat_web/router.ex
scope "/", LvChatWeb do
pipe_through [:browser]
...
resources "/users", UserController, only: [:new, :create] ...
end
/lib/lv_chat_web/controllers/
に user_controller.ex
というファイルを作成し、Routerで記述したnew/2
と create/2
を作ります。
それぞれの関数は以下のタイミングで呼ばれます。
関数 | url | method | タイミング |
---|---|---|---|
new | /users/new | GET | 新規登録ページの取得時 |
create | /users/ | POST | submitボタン押下時 |
# /lib/lv_chat_web/controllers/user_controller.ex
use LvChatWeb, :controller
alias LvChat.Accounts
alias LvChat.Accounts.User
def new(conn, _params)do
changeset = Accounts.change_registration(%User{}, %{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"user" => user_params}) do
case Accounts.register_user(user_params) do
{:ok, user} -> conn
|> LvChatWeb.Auth.login(user)
|> put_flash(:info, "#{user.name} が作成されました!")
|> redirect(to: Routes.page_path(conn, :index))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
create/2
の結果により、以下の2パターンへリダイレクトされます。
register_userの戻り | 処理 |
---|---|
成功 | ログイン処理後所定のページにリダイレクト |
失敗 | もう一度登録ページへ |
lib/lv_chat_web/views/
に user_view.ex
というファイルを作成しそこに実装します。
といっても現時点ではほとんど記述がありません。
# /lib/lv_chat_web/views/user_view.ex
defmodule LvChatWeb.UserView do
@moduledoc false
use LvChatWeb, :view
end
/lib/lv_chat_web/templates/
に user
というディレクトリを作成し、 そこに new.html.eex
というファイルを作成します。
# /lib/lv_chat_web/templates/user/new.html.eex
<h1>New User</h1>
<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div>
<%= text_input f, :name, placeholder: "Name" %>
<%= error_tag f, :name %>
</div>
<div>
<%= password_input f, :password, placeholder: "Password" %>
<%= error_tag f, :password %>
</div>
<%= submit "Create User" %>
<% end %>
これでユーザー登録ページの作成が完了しました。
以下のコマンドによりサーバーを起動します。
$ mix phx.server
起動したら以下ブラウザで以下へアクセスし、動作を確認します。
http://localhost:4000/users/new
以上で、ユーザー登録機能まで作成できました。
次回は認証機能の作成を行います。