Source
https://github.com/aasm/aasm
說明
定義 state and event
首先需要有狀態,以及事件。事件為從 A 狀態轉變到 B 狀態。
aasm do
state :sleeping, :initial => true
state :running, :cleaning
event :run do
transitions :from => :sleeping, :to => :running
end
event :clean do
transitions :from => :running, :to => :cleaning
end
event :sleep do
transitions :from => [:running, :cleaning], :to => :sleeping
end
end
然後
job = Job.new
job.sleeping? # => true
job.may_run? # => true
job.run
job.running? # => true
job.sleeping? # => false
job.may_run? # => false
job.run # => raises AASM::InvalidTransition
如果希望他不要跳其他訊息,只要 true
false
就好的話可以用 :whiny_transitions => false
aasm :whiny_transitions => false do
...
end
那麼
job.may_run? # => false
job.run # => false(這裡就不會有錯誤訊息了)
也可以在 event 上加 block
job.run do
job.user.notify_job_ran
end
這樣在 job.run
成功之後就會再執行 job.user.notify_job_ran
了
Callback
你可以為 transitions 設一些 callback,像是 after
,下面這個範例在執行完 run
之後就可以去做 notify_somebody
。
class Job
include AASM
aasm do
state :sleeping, :initial => true, :before_enter => :do_something
state :running
state :finished
event :run, :after => :notify_somebody do
transitions :from => :sleeping, :to => :running, :after => Proc.new {|*args| set_process(*args) }
transitions :from => :running, :to => :finished, :after => LogRunTime
end
end
def notify_somebody
...
end
end
Transitions
一個 event 裡可以有不只一個 transitions,但是如果是從同一個狀態出發的第一個 transitions 成功之後,後面的 transitions 就不會再執行了
Guards
如果符合 guard
裡的條件才做這個 transitions,所以可以用來區隔同一個狀態根據不同過程會到不一樣的下一個狀態。
aasm_event :completes do
transitions :from => sleeping, :to => :running, :guard => condition?
transitions :from => sleeping, :to => :cleaning
end
def condition?
some_contition
end
Bang events
- 有無
!
的差別:
job.run # not saved,只是回傳告知是否可以做這個動作
job.run! # saved
- 不做驗證
aasm :skip_validation_on_save => true do
state :sleeping, :initial => true
state :running
event :run do
transitions :from => :sleeping, :to => :running
end
event :sleep do
transitions :from => :running, :to => :sleeping
end
end
- 不能直接改變狀態
aasm :no_direct_assignment => true do
state :sleeping, :initial => true
state :running
event :run do
transitions :from => :sleeping, :to => :running
end
end
那麼
job.aasm_state # => 'sleeping'
job.aasm_state = :running # => raises AASM::NoDirectAssignmentError
↑ 這裡無法直接將狀態改變
job.aasm_state # => 'sleeping'
Column name
AASM 預設用 aasm_state
去存 state,但是可以透過 override 的方式指定你想要用的 cloumn
class Job < ActiveRecord::Base
include AASM
aasm :column => 'my_state' do
...
end
end
使用
說明
商城舉辦活動 (Activity),活動中顧客會成立訂單 (Order),而訂單中有商品 (Item)
這時就可以使用 AASM 來管理這三樣東西的狀態!
活動 (Activity) 有 active/inactive/pending 三種狀態。
訂單 (Order) 有 pending/inprogress/completed/deleted 四種狀態。
商品 (Item) 有 active/inactive/pending 三種狀態。
狀態情境:
- 如果活動開始,就要將 Activity 調成 active。
- 客戶必須在活動期間內才能成立訂單。
- 訂單中的商品必須是可以訂購 (active) 的狀態。
- 商品被訂購後須確認是否還有庫存,若無,要將狀態調為 inactive。
model/activity.rb
class Activity < ApplicationRecord
include AASM
aasm whiny_transitions: false, column: :state do
state :pending, initial: true
state :active
state :inactive
event :activate do
transitions :from => [:pending, :inactive], :to => :active
end
event :deactivate do
transitions :from => :active, :to => :inactive
end
end
end
model/order.rb
class Order < ApplicationRecord
include AASM
aasm whiny_transitions: false, column: :state do
state :pending, initial: true
state :inprogress
state :completed
state :deleted
event :placed, :after_commit => :deactivate_item do
transitions :from => :pending, :to => :inprogress do
guard do
activity.active? and item.active?
end
end
end
end
def deactivate_item
if item.count = 0
item.deactivate!
end
end
end
model/item.rb
include AASM
aasm whiny_transitions: false, column: :state do
state :pending, initial: true
state :active
state :inactive
event :activate do
transitions :from => [:pending, :inactive], :to => :active
end
event :deactivate do
transitions :from => :active, :to => :inactive
end
end
操作
$ activity.activate!
於是,活動就開始啦!我們的條件一也就完成啦!
接著,客戶建立了一筆訂單。那麼在 order.placed!
裡有
guard do
activity.active? and item.active?
end
是要確保這個目前活動進行中 (也就是 Activity 狀態為 active ),以及客戶所訂購的商品有庫存 (也就是 Item 狀態為 active )。條件都符合時才能執行 order.placed!
並且,在 order.placed!
後有 aftercommit ,所以如果成功執行 order.placed!
,將 Order 的狀態改為 inprogress 的話,就會執行 `deactivateitem這個 method。如果檢查確認 Item 數量已為 0,那麼就會
item.deactivate!`
所以,條件二三四也都完成啦!
結語
介紹與簡易的 AASM 實作就這樣啦,有這些 transcation 就不用擔心在各個 model state會互相影響的複雜狀態下,調整了 A model 卻忘記調 B model 等等的狀況啦。
👩🏫 課務小幫手:
✨ 想掌握 Ruby on Rails 觀念和原理嗎?
我們有開設 🏓 Ruby on Rails 實戰課程 課程唷 ❤️️