本課時(shí)講解 Controller 中的回調(diào),權(quán)限控制,及如何實(shí)現(xiàn)網(wǎng)店的購(gòu)物車和支付功能,以及使用 datatable 查看訂單數(shù)據(jù)。
和 Model 中的回調(diào)一樣,Controller 中也有回調(diào)。Rails 4 之前,它稱作過(guò)濾器,F(xiàn)ilter,現(xiàn)在一些文檔也在使用 filter 字樣。
回調(diào)它之前的名字是 xxx_filter,但是這種稱呼很是歧義,于是在 Rails 4 中改成了 xxx_action。
Controller 中的回調(diào)有三個(gè),before,after,around。并且可以通過(guò) :only 和 :except 指定在哪些方法上應(yīng)用該回調(diào)。
在我們的項(xiàng)目里,為了使登錄用戶才能訪問(wèn),我們?cè)?application_controller.rb 中已經(jīng)使用了一個(gè)前置回調(diào):
class ApplicationController < ActionController::Base
...
before_action :authenticate_user!
...
end
因?yàn)槠渌?Controller 都繼承自它,所以這個(gè)前置回調(diào)會(huì)在所有 Controller 中生效。也就是說(shuō),訪問(wèn)所有頁(yè)面,都需要登錄狀態(tài)。
但是對(duì)于首頁(yè),展示頁(yè)等,可以公開(kāi)訪問(wèn)的頁(yè)面,我們需要跳過(guò)這個(gè)登錄校驗(yàn),Controller 中還可以使用 skip_before_action :xxx 跳過(guò)回調(diào)。
class ProductsController < ApplicationController
skip_before_action :authenticate_user!, only: [:index, :show, :top]
end
回調(diào)也可以使用 block 和單獨(dú)的回調(diào)類,方法和 Model 中一樣,或者參考這里。(注:它還在用 filter 字樣)
Controller 除了對(duì)請(qǐng)求作出相應(yīng),另一個(gè)重要的事情是做權(quán)限控制,只有擁有權(quán)限的用戶才可以觸發(fā)方法。權(quán)限管理有很多 gem 可用,常用的有 cancan,pundit 等。
由于 cancan 已經(jīng)兩年沒(méi)有維護(hù)了,所以Ruby社區(qū)推出cancan 的社區(qū)版 cancancan。
% rails g cancan:ability
create app/models/ability.rb
編輯 ability.rb,我們的權(quán)限是:當(dāng)一個(gè) user(已登錄)字段 role 是 admin 時(shí),可以管理所有資源,否則,只能管理它自己的資源。
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, :all [1]
can :manage, Address, :user_id => user.id [2]
end
[1] 非管理員可讀所有
[2] 用戶管理自己的收貨地址
我們給 users 表添加 role 字段:
rails g migration addRoleToUsers role:string
在視圖中判斷權(quán)限:
<%= link_to "Edit", edit_product_path(product) if can? :update, product %>
這里有四個(gè)動(dòng)作可以判斷::read,:create,:update,:destroy。
我們?cè)?Controller 中增加 load_and_authorize_resource 回調(diào),這個(gè)回調(diào)將自動(dòng)加載一個(gè)資源,并且進(jìn)行權(quán)限校驗(yàn),這適合資源管理中的方法:
class ProductsController < ApplicationController
load_and_authorize_resource
也可以將這個(gè)回調(diào)分成兩個(gè)回調(diào),這樣方便覆寫(xiě)其中的方法:
class ProductsController < ApplicationController
load_resource
authorize_resource
更多文檔詳見(jiàn) 這里。
也可以不實(shí)用回調(diào),直接在方法上判斷權(quán)限,比如判斷當(dāng)前用戶是否可以創(chuàng)建商品:
class ProductsController < ApplicationController
...
def create
authorize! :create, @product
...
cancancan 更多用法,詳見(jiàn) wiki。
購(gòu)物車有多種設(shè)計(jì)思路,有的會(huì)把信息保存在 cookie 中,有的保存在數(shù)據(jù)庫(kù)中。
我們將它保存到數(shù)據(jù)庫(kù)中,使用 CartItem 這個(gè) Model。當(dāng)向購(gòu)物車增加商品時(shí),我們將商品的商品類型(Variant)以及數(shù)量保存到購(gòu)物車中。如果再次購(gòu)買,會(huì)增加該商品類型的數(shù)量。
我們將訂單的創(chuàng)建過(guò)程分為三步,第一步:確認(rèn)購(gòu)物車,第二步:填寫(xiě)收貨地址,第三部:形成訂單,第四部:支付,第五步:支付成功后通知訂單。
為了方便管理購(gòu)物和支付流程,我把這個(gè)邏輯單獨(dú)的放置在 checkout_controller.rb。
當(dāng)我們計(jì)算購(gòu)物車和商品類型價(jià)格的時(shí)候,經(jīng)常的出現(xiàn) line_item.variant.price,這種查詢可以通過(guò) Model 中的 delegate 進(jìn)行改進(jìn):
class LineItem < ActiveRecord::Base
...
delegate :price, to: :variant, prefix: true
這樣,剛才的查詢可以改為 line_item.variant_price。delegate 方法的 api 在 這里。
但是,這種方法會(huì)造成過(guò)多的查詢,所以在確定使用這種方法后,我們可以使用 has_many 中的 includes 選項(xiàng):
class Order < ActiveRecord::Base
has_many :line_items, -> { includes :variant }
end
當(dāng)我們?cè)俅尾樵?line_items 時(shí),會(huì)自動(dòng)的檢索關(guān)聯(lián)的 variant,避免多余的 sql 查詢。
我們編寫(xiě)代碼的時(shí)候,有一些代碼可能需要優(yōu)化,有一些功能還待完成,這時(shí)可以在代碼中增加特殊的注釋:
def checkout
# OPTIMIZE
# TODO
# FIXME
使用 rake 命令可以查看代碼中的注解
rake notes:optimize/fixme/todo
關(guān)注購(gòu)物車的其他環(huán)節(jié),我們可以查看代碼演示,它所使用的方法,我們之前已經(jīng)介紹過(guò)了。
訂單創(chuàng)建時(shí),它的 payment_state 為 confirm,當(dāng)完成支付后,它的狀態(tài)改為 paid。這里我們使用支付寶來(lái)支付訂單。
我們需要安裝支付寶的 gem。
并且增加初始配置文件 config/initializers/alipay.rb,這里需要填寫(xiě)從支付寶商家服務(wù) 申請(qǐng) 的 PID 和 KEY。
Alipay.pid = '申請(qǐng)的 PID'
Alipay.key = '申請(qǐng)的 KEY'
支付寶常用實(shí)時(shí)到賬和擔(dān)保交易,如果開(kāi)通了支付寶快捷登陸,在使用實(shí)時(shí)到賬時(shí),可以掃描二維碼支付。
支付成功后,通常設(shè)定為跳轉(zhuǎn)回訂單詳細(xì)頁(yè)面,支付寶會(huì)通過(guò)接口自動(dòng)通知 notify 方法,我們應(yīng)該在該方法中更新訂單狀態(tài),并且通知支付寶是否成功,只需 render text: "success" 或 render text: "fail"。
這里有一份非常詳盡的支付寶集成方案,歡迎參考。
進(jìn)入到“我的訂單”頁(yè)面,會(huì)有多條訂單記錄,這里需要對(duì)訂單進(jìn)行分頁(yè)。常用的分頁(yè) gem 是 will_paginate。因?yàn)槲覀冊(cè)谑褂?bootstrap,所以需要安裝 will_paginate-bootstrap。
分頁(yè)的代碼非常簡(jiǎn)單:
class OrdersController < ApplicationController
...
def index
@orders = Order.paginate(:page => params[:page], :per_page => 20)
頁(yè)面上:
<div class="well">
<%= page_entries_info @orders %>
</div>
<%= will_paginate @orders, renderer: BootstrapPagination::Rails %>
為了讓 page_entries_info 方法和分頁(yè)按鈕顯示中文,我們?cè)黾右粋€(gè)新的語(yǔ)言包:
config/locales/will_paginate/zh-CN.yml
除了 will_paginate,還有 kaminari,以及 datatable
datatable 是傳統(tǒng)分頁(yè)方法的一個(gè)極好的替代,當(dāng)數(shù)據(jù)量較多,且需要 ajax 加載數(shù)據(jù)時(shí),可以使用 server 端 datatable 實(shí)現(xiàn),具體請(qǐng)參考 示例列表。
當(dāng)我們的訂單數(shù)量巨大的時(shí)候,我們需要使用 datatable 的 server-side,來(lái)減輕分頁(yè)加載時(shí)的壓力。這里有一個(gè)演示,供大家參考。