iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 21
1

祝各位聖誕佳節愉快!

建立User model

在1.3.0以後拿掉了mix phoenix.gen.model,所以我們改用mix phx.gen.schema,基本上是做一樣的事情:

$ mix phx.gen.schema User users username password encrypted_password email
* creating lib/shop/user.ex
* creating priv/repo/migrations/20171224221535_create_users.exs

產生兩個檔案:user.ex20171224221535_create_users.exs
編輯一下migration檔,讓username與email不可為null,並且設定email必須唯一(unique):

defmodule Shop.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :username, :string, null: false
      add :password, :string
      add :encrypted_password, :string
      add :email, :string, null: false

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

接著我們編輯user.ex,讓password不會進資料庫(因為我們希望只有加密後的密碼存進資料庫)。在password後加上virtual: true,並且把changeset裡面的password與encrypted_password拿掉:

defmodule Shop.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias Shop.User


  schema "users" do
    field :email, :string
    field :encrypted_password, :string
    field :password, :string, virtual: true
    field :username, :string

    timestamps()
  end

  @doc false
  def changeset(%User{} = user, attrs) do
    user
    |> cast(attrs, [:username, :email])
    |> validate_required([:username, :email])
  end
end

接著我們處理密碼加密的部分,我們會用到comeonin這個mix library(可以理解為ruby gem),打開mix.exs(可以理解為Gemfile)加上:comeonin

  ...略...
  def application do
    [
      mod: {Shop.Application, []},
      extra_applications: [:logger, :runtime_tools, :comeonin]
    ]
  end

  defp deps do
    [
      {:phoenix, "~> 1.3.0"},
      {:phoenix_pubsub, "~> 1.0"},
      {:phoenix_ecto, "~> 3.2"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 2.10"},
      {:phoenix_live_reload, "~> 1.0", only: :dev},
      {:gettext, "~> 0.11"},
      {:cowboy, "~> 1.0"},
      {:comeonin, "~> 2.0"}
    ]
  end

然後輸入指令mix deps.get安裝

接下來我們回到lib/shop/user.ex,把密碼驗證與加密補上,貼上這兩個方法:

  def registration_changeset(model, params) do
    model
    |> changeset(params)
    |> cast(params, ~w(password), [])
    |> validate_length(:password, mix: 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, :encrypted_password, Comeonin.Bcrypt.hashpwsalt(pass))
      _ ->
        changeset
    end
  end

這樣User model的部分就告一段落囉!

建立 User controller

很遺憾,Phoenix沒有單獨產生controller的方法。首先我們先新增路由:

  scope "/", ShopWeb do
    pipe_through :browser # Use the default browser stack
    resources "/users", UserController, only: [:new, :create]
    get "/", PageController, :index
  end

mix phx.routes來確認一下路由是不是如我們所預期:

user_path  GET     /users/new                ShopWeb.UserController :new
user_path  POST    /users                    ShopWeb.UserController :create

接著我們手動新增一個檔案lib/shop_web/controllers/user_controller.ex

defmodule ShopWeb.UserController do
  use ShopWeb.Web, :controller
  alias ShopWeb.User
  alias Shop.Repo

  def new(conn, _params) do
    changeset = User.changeset(%User{}, _params)
    render conn, "new.html", changeset: changeset
  end

  def create(conn, %{"user" => user_params}) do
    changeset = User.registration_changeset(%User{}, user_params)

    case Repo.insert(changeset) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "#{user.username} created")
        |> redirect(to: page_path(conn, :index))

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

以及lib/shop_web/views/user_view.ex

defmodule ShopWeb.UserView do
  use ShopWeb, :view

end

還有lib/shop_web/templates/user/new.html.eex

<h1>Create an account</h1>

<%= if @changeset.action do %>
  <div class="alert alert-danger">
    <p>Oops! something went wrong!</p>
  </div>
<% end %>

<%= form_for @changeset, user_path(@conn, :create) , fn f -> %>
  <div class="form-group">
    <%= text_input f, :email, placeholder: "E-mail", class: "form-control" %>
    <%= error_tag f, :name %>
  </div>
  <div class="form-group">
    <%= text_input f, :username, placeholder: "Name", class: "form-control" %>
    <%= error_tag f, :username %>
  </div>
  <div class="form group">
    <%= password_input f, :password, placeholder: "Password", class: "form-control" %>
    <%= error_tag f, :password %>
  </div>
  <br>
  <%= submit "Create account", class: "btn btn-primary" %>
<% end %>

接下來假如我們訪問 http://localhost:4000/users/new ,就可以看到剛剛的頁面:
create user
讓我們創建一個新會員,然後會跳轉到首頁出現成功的訊息:
created success

假如我們用iex -S mix檢查一下,可以發現資料已經確實寫入資料庫,而且密碼有加密處理:

> Repo.all(User)
[debug] QUERY OK source="users" db=2.7ms decode=3.7ms
SELECT u0."id", u0."email", u0."encrypted_password", u0."username", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
[%Shop.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "new_user@email.com",
  encrypted_password: "$2b$12$ET6V7r.6qi/3xoS4M2U9auXXJOTLl/kcME39g0gyLkAoxILWebgQK",
  id: 1, inserted_at: ~N[2017-12-25 01:52:05.179589], password: nil,
  updated_at: ~N[2017-12-25 01:52:05.181295], username: "bater"}]

上一篇
Phoenix起步走:建立一個購物網站--後台
下一篇
Phoenix起步走:建立一個購物網站--登入
系列文
新時代的網頁框架比較-- 淺談Rails、Django、Phoenix、Laravel31

尚未有邦友留言

立即登入留言