祝各位聖誕佳節愉快!
在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.ex
與20171224221535_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的部分就告一段落囉!
很遺憾,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 ,就可以看到剛剛的頁面:
讓我們創建一個新會員,然後會跳轉到首頁出現成功的訊息:
假如我們用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"}]