网上商店,消费者下单后,形成了订单,在不同情形下,订单的状态也会不同。因此,我们需要有一个功能,能使订单从某种状态切换(变化)成另一种状态。这就是订单状态的切换功能,而aasm状态机正是实现这一功能的工具。
逻辑梳理
在开始实现这种功能,使用aasm状态机之前。我们需要先问自己几个问题:
- 订单状态有多少种
- 订单切换事件有多少种
- 在什么情况下,触发订单切换事件
弄清楚这些问题,理清它们之间的逻辑关系,有利于我们规划使用aasm,达到功能切换的目的。
我先放一张图,来形象化描述问题1、问题2:
进一步阐释
- 订单状态
- 种类: 6种
- 分别是: order_placed, paid, shipping, shipped, good_returned, order_cancelled
- 订单切换事件
- 定义:它指的是订单从一种或几种状态切换/转变成另一种状态的过程
- 种类:5种
- 分别是:A, B, C, D, E
- 举例说明:比如A事件的发生,意味着订单状态从order_placed改变为paid,最终结果是订单状态为paid
- 触发订单切换事件的条件
- 定义:它指的是,在满足某种条件或情形下,我们就会去触发订单切换事件;从行为的先后顺利角度看,执行订单切换事件行为的“前一个行为”就是“触发订单切换事件的条件”
- 举例说明:比如触发A事件的条件,是消费者下完订单、点击付款提交后
具体案例
Part I
解决问题1和问题2,即安装aasm以及定义订单状态和订单切换事件
Step1: 安装aasm
gem 'aasm'
执行bundle install
rails s
Step2: 在orders table里,产生aasm_state栏位,用来记录订单状态
执行rails g migration add_aasm_state_to_orders
在刚才的migration 文件里,增加两行
add_column :orders, :aasm_state, :string
add_index :orders, :aasm_state
执行rake db:migrate
Step3: 在app/models/order.rb文件里,增加以下内容
include AASM
aasm do
state :order_placed
state :paid
state :shipping
state :shipped
state :good_returned
state :order_cancelled
event :make_payment do
transitions from: :order_placed, to: :paid
end
event : ship do
transitions from: :paid, to: :shipping
end
event :deliver do
transitions from: :shipping, to: :shipped
end
event :return_good do
transitions from: :shipped, to: :good_returned
end
event :cancel_order do
transitions from: [:order_placed, :paid], to: :order_cancelled
end
end
说明:以上步骤,解决了问题1、问题2,
Part II
解决问题3,定义触发订单切换事件的条件,此处分别针对触发事件A, B, C, D, E的条件
(一)定义触发订单切换事件A的条件
条件是:消费者下定单且提交付款之后
Step1: 定义好routing,在member do下面加入两行
resources :orders do
member do
post :pay_with_alipay
post :pay_with_wechat
end
end
Step2: 付款这个action,通常是在app/controllers/orders_controller.rb里定义的,比如是用支付宝或微信付款,则这个action可以加入一行: @order.make_payment
def pay_with_alipay
@order = Order.find(params[:id])
@order.set_payment_with("alipay")
@order.make_payment
flash[:notice] = "已用支付宝完成付款"
redirect_to order_path(@order)
end
def pay_with_wechat
@order = Order.find(params[:id])
@order.set_payment_with("wechat")
@order.make_payment!
flash[:notice] = "已用微信完成付款"
redirect_to order_path(@order)
end
Step3: 在app/models/order.rb里,分别定义和支付相关的两个方法
def set_payment_with(method)
self.update_columns(payment_method: method)
end
def pay!
self.update_columns(is_paid: true)
end
Step3: 事件A的触发条件,包括两个:make_payment 以及 after_commit: :pay!
所以在order.rb里,重新定义事件A
event :maket_payment, after_commit: :pay! do
transitions from: :order_placed, to: :paid
end
(二)定义触发订单切换事件B, C, D, E的条件
B: 管理员在后台点击“出货”按钮
C: 管理员在后台点击“已出货”按钮
D: 管理员在后台点击“退货”按钮
E: 管理员在后台点击“取消订单”按钮
Step1: 定义好routing, 在member do下面加入四行,这四行分别对应以上“触发订单切换事件B, C, D, E 条件"的routing
namespace :admin do
resources :orders do
member do
post :ship
post :shipped
post :return
post :cancel
end
end
end
Step2: 定义好以上条件的action,在app/controllers/admin/orders_controller.rb文件里,写以下代码,让action里包含了事件的执行
def ship
@order = Order.find(params[:id])
@ordr.ship!
redirect_to admin_order_path(@order)
end
def shipped
@order = Order.find(params[:id])
@ordr.deliver!
redirect_to admin_order_path(@order)
end
def return
@order = Order.find(params[:id])
@ordr.return_good!
redirect_to admin_order_path(@order)
end
def cancel
@order = Order.find(params[:id])
@ordr.cancel_order!
redirect_to admin_order_path(@order)
end
Step3: 写好view,让每个订单可以因其不同状态被管理员在后台点击切换
新增一个文件touch app/views/admin/orders/_state.html.erb
在该文件里,写入
<div style="padding: 10px; float: right; ">
<% case order.aasm_state do %>
<% when "order_placed" %>
<%= link_to("取消订单", cancel_admin_order_path(order), method: :post, class: "btn btn-default btn-sm") %>
<% when "paid" %>
<%= link_to("取消订单", cancel_admin_order_path(order), method: :post, class: "btn btn-default btn-sm") %>
<%= link_to("出货", ship_admin_order_path(order), method: :post, class: "btn btn-default btn-sm") %>
<% when "shipping" %>
<%= link_to("已出货", shipped_admin_order_path(order), method: :post, class: "btn btn-default btn-sm") %>
<% when "shipped" %>
<%= link_to("退货", return_admin_order_path(order), method: :post, class: "btn btn-default btn-sm") %>
<% when "good_returned" %>
<span class="label label-default">已退货</span>
<% when "order_cancelled" %>
<span class="label label-default">已取消订单</span>
<% end %>
<div>
在app/views/admin/orders/show.html.erb里,新增一行
<%= render "state", order: @order %>
总结
经过逻辑梳理,理解运作机制,能帮助我们事先规划好,便于后续实作代码。
通过具体案例,运用上面的逻辑梳理,落到实处写代码,实现订单状态切换的功能。