2 - 後台管理
一切就緒後,首先從後台開始做 story: 「管理者要有一個後台」 => 有 localhost:4000/admin/products 這個網址 => 修改 router.ex
# web/router.ex
...
scope "/admin", ShoppingSite.Admin, as: :admin do
pipe_through :browser
resources "/products", ProductController
end
...
第一行 ShoppingSite.Admin
讓下面的 resource都不用再寫前綴
後面的 as: :admin
讓 path前面都加上 admin_
(如下圖)
避免同一個 resource的同名混淆
用 mix phoenix.routes
看看現在網址是我們要的嗎?
page_path GET / ShoppingSite.PageController :index
admin_product_path GET /admin/products ShoppingSite.Admin.ProductController :index
admin_product_path GET /admin/products/:id/edit ShoppingSite.Admin.ProductController :edit
admin_product_path GET /admin/products/new ShoppingSite.Admin.ProductController :new
admin_product_path GET /admin/products/:id ShoppingSite.Admin.ProductController :show
admin_product_path POST /admin/products ShoppingSite.Admin.ProductController :create
admin_product_path PATCH /admin/products/:id ShoppingSite.Admin.ProductController :update
PUT /admin/products/:id ShoppingSite.Admin.ProductController :update
admin_product_path DELETE /admin/products/:id ShoppingSite.Admin.ProductController :delete
great~~
story:「產品要有標題、文字、數量、敘述」=>
產生 product model
command: mix phoenix.gen.model product products title:string description:text quantity:integer price:integer
修改 product model檔案, 讓 changeset會去檢查是否四項都是有輸入, 然後弄成以後可以擴充圖片
# web/models/product.ex
...
@required_fields ~w(title description quantity price)
@optional_fields ~w()
@doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(model, params \\ %{}) do
model
|> cast(params, @required_fields, @optional_fields)
|> validate_required([:title, :description, :quantity, :price]) #must be atom
|> validate_length(:description, max: 200)
end
migration 指定資料輸入時不可為空
# priv/repo/migration/XXXXX_create_product.exs
...
def change do
create table(:products) do
add :title, :string, null: false
add :description, :text, null: false
add :quantity, :integer, null: false
add :price, :integer, null: false
timestamps()
end
end
...
then mix ecto.migrate
!
我們可以檢查一下我們寫的 changeset 到底對不對
iex> alias ShoppingSite.Product
iex> params = %{title: "product_1", description: "test", price: 100, quantity: 1}
iex> changeset = Product.changeset(%Product{}, params)
#Ecto.Changeset ....
iex> changeset.valid?
true
有顯示 true 表示 changeset 有成功
new、create in controller
新增 web/controllers/admin
資料夾
新增 web/controllers/admin/product_controller.ex
# web/controllers/admin/product_controller.ex
defmodule ShoppingSite.Admin.ProductController do
use ShoppingSite.Web, :controller
alias ShoppingSite.Product
def new(conn, params) do
changeset = Product.changeset(%Product{})
render conn, "new.html", changeset: changeset
end
def create(conn, %{"product" => product_params}) do
changeset = Product.changeset(%Product{}, product_params)
case Repo.insert(changeset) do
{:ok, _product} ->
conn
|> put_flash(:info, "Add product successfully")
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
end
增加 product_view.ex, 因為在 router檔案我們是寫 ShoppingSite.Admin 所以 view, template都要新增 admin 資料夾,phoenix才讀得到
# web/views/admin/product_view.ex
defmodule ShoppingSite.Admin.ProductView do
use ShoppingSite.Web, :view
end
把輸入產品規格的表格做成可以重複使用的 form.html
#web/templates/admin/form.html.eex
<%= form_for @changeset, @action , fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops! something went wrong!</p>
</div>
<%= end %>
<div class="form-group">
<%= label f, :title, class: "control-label" %>
<%= text_input f, :title, class: "form-control" %>
<%= error_tag f, :title %>
</div>
<div class="form-group">
<%= label f, :description, class: "control-label" %>
<%= text_input f, :description, class: "form-control" %>
<%= error_tag f, :description %>
</div>
<div class="form-group">
<%= label f, :price, class: "control-label" %>
<%= text_input f, :price, class: "form-control" %>
<%= error_tag f, :price %>
</div>
<div class="form-group">
<%= label f, :quantity, class: "control-label" %>
<%= text_input f, :quantity, class: "form-control" %>
<%= error_tag f, :quantity %>
</div>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
新增 new.html.eex
#web/templates/admin/new.html.eex
<h1>Create new product</h1>
<%= render "form.html", changeset: @changeset, action: admin_product_path(@conn, :create) %>
<%= link "Back" , to: page_path(@conn, :index) %>
所有 product 清單
#web/controllers/product_controller.ex
def index(conn, _params) do
products = Repo.all(Product)
render conn, "index.html", products: products
end
新增 index.html 注意 for 是要用 <%= %> 括起來....
# web/templates/admin/product/index.html.eex
<ul>
<%= for product <- @products do %>
<li><%= link product.title , to: admin_product_path(@conn, :show, product.id) %></li>
<% end %>
</ul>
到這裡就可以上架商品囉!
在網站試試看,submit完在 console輸入ShoppingSite.Repo.all ShoppingSite.Product
要可以看到剛剛上傳的資料