三十天鐵人賽的倒數第二天,我想盡可能完成購物網站,但感覺有些難度。今天我會繼續進行下去,但是略過大部份的前端程式碼複製貼上,只保留重要的後端指令。關於前端的部分,可以直接參考我的github。
首先我們需要有購物車,裡面有多個購物車物件,每個物件對應一種商品:
$ mix phx.gen.schema Cart carts
$ mix phx.gen.schema CartItem cart_items cart_id:references:carts product_id:references:products
產生migrate的時候,可以直接綁定與其他model的關係:CartItem對應Cart cart_id:references:carts
,CartItem對應Product product_id:references:products
。記得model內需要手動修正:
# lib/shop/cart.ex
...
schema "carts" do
has_many :cart_items, Shop.CartItem, on_delete: :delete_all
has_many :products, through: [:cart_items, :product]
timestamps()
end
...
# lib/shop/cart_item.ex
...
schema "cart_items" do
belongs_to :cart, Shop.Cart
belongs_to :product, Shop.Product
timestamps()
end
...
記得執行migrate。接著我們新增一個新的plug來實作購物車:
# lib/shop_web/plugs/cart_plug.ex
defmodule ShopWeb.CartPlug do
import Plug.Conn
alias Shop.{Repo, Cart}
def init(default), do: default
def call(conn, _params) do
conn
|> find_cart
end
def find_cart(conn) do
cart_id = get_session(conn, :cart_id)
if cart_present?(cart_id) do
case Repo.get(Cart, cart_id) do
nil ->
conn
|> create_cart
cart ->
conn
|> put_session(:cart_id, cart_id)
|> assign(:current_cart, cart)
|> configure_session(renew: true)
end
else
conn
|> create_cart
end
end
def create_cart(conn) do
changeset = Cart.changeset(%Cart{}, %{})
case Repo.insert(changeset) do
{:ok, cart} ->
conn
|> put_session(:cart_id, cart.id)
|> assign(:current_cart, cart)
|> configure_session(renew: true)
{:error, _} ->
conn
end
end
def cart_present?(res) do
case res do
nil -> false
_ -> true
end
end
end
並且寫一個cart_controller
defmodule ShopWeb.CartController do
use ShopWeb, :controller
def current_cart(conn) do
conn.assigns.current_cart
end
end
最後像處理使用者權限的auth一樣,加在router的plug內:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug ShopWeb.Auth, repo: Shop.Repo
plug ShopWeb.CartPlug
end
最後在view的地方新增:
# lib/shop_web/views/layout_view.ex
defmodule ShopWeb.LayoutView do
use ShopWeb, :view
import ShopWeb.CartController, only: [current_cart: 1]
def cart_item_count(conn) do
Shop.Repo.preload(current_cart(conn), :cart_items).cart_items
|> length
end
end
就可以在navbar產生我們需要的購物車數量顯示了!
<li class="nav-item"><a href="#" class="nav-link">Cart (<%= cart_item_count(@conn) %>)</a></li>
從瀏覽器看起來像這樣,但目前他永遠是0,因為我們還沒有寫新增的方法。