Posted by: Jake Dempsey on: January 19, 2010
This morning I ran into a small roadblock working with the acts_as_state_machine gem. My code started like this:
class Communication < ActiveRecord::Base
include AASM
aasm_column :status
aasm_state :draft
aasm_state :pending
aasm_state :approved
aasm_state :rejected
aasm_event :submit do
transitions :to => :pending, :from => :draft
end
aasm_event :approve do
transitions :to => :approved, :from => [:pending, :rejected]
end
aasm_event :reject do
transitions :to => :rejected, :from => [:pending, :approved], :guard => :validate_rejection_reason
end
aasm_initial_state :draft
def validate_rejection_reason
if self.rejection_reason
true
else
raise AASM::InvalidTransition.new('A rejection reason is required')
end
end
end
This is a basic example using AASM to provide an approval process for my Communication model. I wanted the ability to just call instance.reject(“some reason”) or instance.reject!(“some reason”). I was really thinking to hard about it. The assm_event just creates some methods for me….so why not just decorate those methods using ruby aliases. This is what I ended up with:
include AASM
aasm_column :status
aasm_state :draft
aasm_state :pending
aasm_state :approved
aasm_state :rejected
aasm_event :submit do
transitions :to => :pending, :from => :draft
end
aasm_event :approve do
transitions :to => :approved, :from => [:pending, :rejected]
end
aasm_event :reject do
transitions :to => :rejected, :from => [:pending, :approved], :guard => :validate_rejection_reason
end
alias old_reject reject
alias old_reject! reject!
aasm_initial_state :draft
def reject(reason)
self.rejection_reason = reason
self.old_reject
end
def reject!(reason)
self.rejection_reason = reason
self.old_reject!
end
def validate_rejection_reason
if self.rejection_reason
true
else
raise AASM::InvalidTransition.new('A rejection reason is required')
end
end
Notice the use of alias for reject and reject!. Now you have to call reject with a rejection reason. There may be another way to get parameters in to the event call….but this is how I rolled mine
Good to know… you could alternatively use alias_method_chain for better readability outside of the model:
# play nice
def reject_with_reason(reason)
if reason.present?
self.rejection_reason = reason
reject_without_reason
else
errors[:rejection_reason] < e
::Logger.warn e.message
end
When you don’t:
@thing = Thing.find(1)
@thing.reject!
Wow, it stripped half my comment
Not nice WordPress.
Maybe this will work…
# play nice
def reject_with_reason(reason)
if reason.present?
self.rejection_reason = reason
reject_without_reason
else
errors[:rejection_reason] << "is required."
false
end
end
# be nasty
def reject_with_reason!(reason)
if reason.present?
self.rejection_reason = reason
reject_without_reason!
else
raise AASM::InvalidTransition.new('A rejection reason is required')
end
end
alias_method_chain :reject, :reason
alias_method_chain :reject!, :reason
Then, when rejecting, you’re able to call reject_with_reason(!) and you still have the ability to bypass a reason if desired. Obviously in your scenario you’re forcing validation, but I’m just pointing out what you could do if passing a reason were optional.
When you want a reason:
@thing = Thing.find(1)
if @thing.reject_with_reason("It was really bad.")
puts "Yay, you did it!"
else
puts @thing.errors
end
@thing = Thing.find(1)
begin
@thing.reject_with_reason!("Fee fi fo fum.")
rescue => e
::Logger.warn e.message
end
When you don’t:
@thing = Thing.find(1)
@thing.reject!
May 27, 2010 at 8:40 am
Very useful, thank you!