Elixir, PhoenixでLoginページを作成


やりたいことはログインページで`email`と`password`を入力して、ログインするとユーザー一覧ページに遷移します。

![スクリーンショット 2015-06-16 13.04.08.png](https://qiita-image-store.s3.amazonaws.com/0/54698/a40e3140-89b4-c4ee-d355-53058cb23b78.png)


![スクリーンショット 2015-06-16 13.04.44.png](https://qiita-image-store.s3.amazonaws.com/0/54698/9fbf7e9f-ab46-41be-d160-b89f327592e7.png)
![スクリーンショット 2015-06-16 13.06.35.png](https://qiita-image-store.s3.amazonaws.com/0/54698/c310064b-34b3-8857-ad00-083bf61e1a2d.png)

まずユーザー一覧ページを用意するためにPhoenixの[Ectoに関するドキュメント](http://www.phoenixframework.org/v0.13.1/docs/ecto-models)を読むといいです。


ログインページは[ネット上で探したもの](https://gist.github.com/bMinaise/7329874)を使っています。その中にBootstrap3が含まれているので、Bootstrap3も使いたいです。自分の[この前の投稿](http://qiita.com/ColdFreak/items/a1cb5f86474986fd4a9e)を参照にするといいです。


* プロジェクトを作成します。`--no-brunch`についての説明は[ここ](http://www.phoenixframework.org/v0.13.1/docs/up-and-running)にあります。

```
13:29:11 [N1210A001 => ~/Playground/elixir]
$ mix phoenix.new hello_phoenix --no-brunch
```

* ユーザー一覧のページを用意します。下のコマンドを実行すると`hello_phoenix/priv/repo/migrations/`ディレクトリの下にmigrationファイルが生成されて、後で使う`$ mix ecto.migrate`コマンドの為です。

```
13:34:30 [N1210A001 => ~/Playground/elixir/hello_phoenix]
$ mix phoenix.gen.html User users name:string email:string password:string
```

* `resources "/users", UserController`を`hello_phoenix/web/router.ex`に追加します。


* `hello_phoenix_dev`データベースを作成します。なぜ`hello_phoenix_dev`を作成するかは`hello_phoenix/config/dev.exs`ファイルの中に定義されています。

`postgres`のroleがないなどのエラー出ている場合は[このページ](http://www.phoenixframework.org/v0.13.1/docs/ecto-models)で答えは探せます。

```
13:37:18 [N1210A001 => ~/Playground/elixir/hello_phoenix]
$ mix ecto.create
```

* 作成されたデーターベスにテーブルを作成します。

```
13:47:53 [N1210A001 => ~/Playground/elixir/hello_phoenix]
$ mix ecto.migrate
```

* これでアプリを立ち上げてみます。下の図のようになっているはずです。

```
13:48:01 [N1210A001 => ~/Playground/elixir/hello_phoenix]
$ mix phoenix.server
```

![スクリーンショット 2015-06-16 13.49.03.png](https://qiita-image-store.s3.amazonaws.com/0/54698/5ef759f7-55b2-e36e-afc4-d7ef1b072336.png)


*　画面上でユーザを複数を作成します。

![スクリーンショット 2015-06-16 13.55.00.png](https://qiita-image-store.s3.amazonaws.com/0/54698/8b3c2e48-97f2-6802-c7bb-c207525726d6.png)


* [Bootstrap3を使えるようにします](http://qiita.com/ColdFreak/items/a1cb5f86474986fd4a9e)。

* `http://localhost:4000/`にアクセスする際にログイン画面にします。

1) `hello_phoenix/web/templates/layout/application.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/bootstrap.css") %>">
     <link rel="stylesheet" href="<%= static_path(@conn, "/css/login.css") %>">
     <link rel="stylesheet" href="<%= static_path(@conn, "/js/bootstrap.js") %>">
   </head>

   <body>
     <%= @inner %>
   </body>
 </html>
```

2) `hello_phoenix/web/templates/page/index.html.eex`を下のように修正します。

```
 <div class="container">
     <div class="row">
         <div class="col-sm-6 col-md-4 col-md-offset-4">
             <div class="account-wall">
               <form class="form-signin" action="<%= page_path(@conn, :login) %>" method="post">
                 <input type="text" class="form-control" name="user[email]" placeholder="Email" required autofocus>
                 <input type="password" class="form-control" name="user[password]" placeholder="Password" required>
                 <input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>">
                 <button class="btn btn-lg btn-primary btn-block" type="submit">
                     Sign in</button>
                 <label class="checkbox pull-left">
                     <input type="checkbox" value="remember-me">
                     Remember me
                 </label>
                 <a href="#" class="pull-right need-help">Need help? </a><span class="clearfix"></span>
                 </form>
             </div>
             <a href="#" class="text-center new-account">Create an account </a>
         </div>
     </div>
 </div>
```

なぜ`<input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>">`を入れているかは[stackoverflowの質問](http://stackoverflow.com/questions/28595153/phoenix-invalid-csrf-cross-site-forgery-protection-token-error)を参照するといいです。この1行を入れないと、下のようなエラーが出ます。
`invalid CSRF (Cross Site Forgery Protection) token, make sure all requests include a '_csrf_token' param or an 'x-csrf-token' header`

![スクリーンショット 2015-06-16 15.09.07.png](https://qiita-image-store.s3.amazonaws.com/0/54698/f73912d5-d1d1-8874-f694-b3639266be8b.png)


3) `login.css`ファイルを下の中身で作成して、`hello_phoenix/priv/static/css/`ディレクトリ下に移動します。

```
.form-signin
{
    max-width: 330px;
    padding: 15px;
    margin: 0 auto;
}
.form-signin .form-signin-heading, .form-signin .checkbox
{
    margin-bottom: 10px;
}
.form-signin .checkbox
{
    font-weight: normal;
}
.form-signin .form-control
{
    position: relative;
    font-size: 16px;
    height: auto;
    padding: 10px;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
.form-signin .form-control:focus
{
    z-index: 2;
}
.form-signin input[type="text"]
{
    margin-bottom: -1px;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}
.form-signin input[type="password"]
{
    margin-bottom: 10px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}
.account-wall
{
    margin-top: 20px;
    padding: 40px 0px 20px 0px;
    background-color: #f7f7f7;
    -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
    -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
    box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}
.login-title
{
    color: #555;
    font-size: 18px;
    font-weight: 400;
    display: block;
}
.profile-img
{
    width: 96px;
    height: 96px;
    margin: 0 auto 10px;
    display: block;
    -moz-border-radius: 50%;
    -webkit-border-radius: 50%;
    border-radius: 50%;
}
.need-help
{
    margin-top: 10px;
}
.new-account
{
    display: block;
    margin-top: 10px;
}
```

4) `hello_phoenix/web/router.ex`ファイルに`/login`を部分を追加して、下のようになります。注意して欲しいのは`post`を使っています。

```
 defmodule HelloPhoenix.Router do
   use HelloPhoenix.Web, :router

   pipeline :browser do
     plug :accepts, ["html"]
     plug :fetch_session
     plug :fetch_flash
     plug :protect_from_forgery
   end

   pipeline :api do
     plug :accepts, ["json"]
   end

   scope "/", HelloPhoenix do
     pipe_through :browser # Use the default browser stack

     get "/", PageController, :index
     post "/login", PageController, :login
     resources "/users", UserController
   end
 end

```

5) これでアプリを起動して、ログインページが表示されるようになっているはずです。

```
14:28:36 [N1210A001 => ~/Playground/elixir/hello_phoenix]
$ mix phoenix.server
```

![スクリーンショット 2015-06-16 14.29.17.png](https://qiita-image-store.s3.amazonaws.com/0/54698/64a59fb2-81c2-43ec-378b-38c025c209e7.png)


* これからログイン処理に入ります。`hello_phoenix/web/controllers/page_controller.ex`ファイルを以下のように修正します。

```
 defmodule HelloPhoenix.PageController do
   use HelloPhoenix.Web, :controller

   # `query = from u in User,`部分
   # の`User`が使える為にaliasしている

   alias HelloPhoenix.User

   # `redirect conn, to: Helpers.user_path(conn, :index)`部分
   # の`Helpers.user_path`が使えるためにaliasをしている

   alias HelloPhoenix.Router.Helpers

   plug :action

   def index(conn, _params) do
     render conn, "index.html"
   end

   def login(conn, %{"user" => %{"email" => email, "password" => password}}) do
     query = from u in User,
               where: u.email == ^email,
               where: u.password == ^password,
               select: u.id
     result = HelloPhoenix.Repo.all(query)
     # ユーザーが存在すればユーザー一覧ページにリダイレクトされます
     case length(result) > 0 do
       true ->
         redirect conn, to: Helpers.user_path(conn, :index)
       false ->
         text conn, "ログイン失敗"
     end
   end
 end
```

これで一番簡単なログインページができました。また実装していない部分は

1) パスワードの暗号化
2) ログインしていないユーザーはユーザー一覧を見られないように

どなたか上のような機能実装したら、教えてください。特に２）番目です。