Skip to content

Commit

Permalink
Merge branch 'stiff-master' into master_merge_stiff
Browse files Browse the repository at this point in the history
Conflicts:
	README.md
	lib/aasm/supporting_classes/event.rb
  • Loading branch information
alto committed Feb 21, 2013
2 parents 497e14e + 1371ae5 commit 6f9be86
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 91 deletions.
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -85,6 +85,12 @@ class Job
end

event :sleep do
after do
...
end
error do |e|
...
end
transitions :from => :running, :to => :sleeping
end
end
Expand Down Expand Up @@ -129,6 +135,9 @@ Also, you can pass parameters to events:

In this case the `set_process` would be called with `:defagmentation` argument.

In case of an error during the event processing the error is rescued and passed to `:error`
callback, which can handle it or re-raise it for further propagation.

### Guards

Let's assume you want to allow particular transitions only if a defined condition is
Expand Down
4 changes: 2 additions & 2 deletions lib/aasm/aasm.rb
Expand Up @@ -184,7 +184,7 @@ def aasm_fire_event(name, options, *args)
persist_successful = true
if persist
persist_successful = aasm_set_current_state_with_persistence(new_state_name)
event.execute_success_callback(self) if persist_successful
event.fire_callbacks(:success, self) if persist_successful
else
self.aasm_current_state = new_state_name
end
Expand Down Expand Up @@ -213,7 +213,7 @@ def aasm_fire_event(name, options, *args)
end
end
rescue StandardError => e
event.execute_error_callback(self, e)
event.fire_callbacks(:error, self, e) || raise(e)
end
end
end
66 changes: 23 additions & 43 deletions lib/aasm/supporting_classes/event.rb
@@ -1,7 +1,7 @@
module AASM
module SupportingClasses
class Event
attr_reader :name, :success, :options
attr_reader :name, :options

def initialize(name, options = {}, &block)
@name = name
Expand Down Expand Up @@ -40,11 +40,8 @@ def all_transitions
@transitions
end

def fire_callbacks(action, record)
action = @options[action]
action.is_a?(Array) ?
action.each {|a| _fire_callbacks(a, record)} :
_fire_callbacks(action, record)
def fire_callbacks(action, record, *args)
invoke_callbacks(@options[action], record, args)
end

def ==(event)
Expand All @@ -55,45 +52,13 @@ def ==(event)
end
end

def execute_success_callback(obj, success = nil)
callback = success || @success
case(callback)
when String, Symbol
obj.send(callback)
when Proc
callback.call(obj)
when Array
callback.each{|meth|self.execute_success_callback(obj, meth)}
end
end

def execute_error_callback(obj, error, error_callback=nil)
callback = error_callback || @error
raise error unless callback
case(callback)
when String, Symbol
raise NoMethodError unless obj.respond_to?(callback.to_sym)
obj.send(callback, error)
when Proc
callback.call(obj, error)
when Array
callback.each{|meth|self.execute_error_callback(obj, error, meth)}
end
end

private

def update(options = {}, &block)
if options.key?(:success) then
@success = options[:success]
end
if options.key?(:error) then
@error = options[:error]
end
@options = options
if block then
instance_eval(&block)
end
@options = options
self
end

Expand Down Expand Up @@ -123,15 +88,23 @@ def _fire(obj, test, to_state=nil, *args)
result
end

def _fire_callbacks(action, record)
case action
def invoke_callbacks(code, record, args)
case code
when Symbol, String
record.send(action)
record.send(code, *args)
true
when Proc
record.instance_eval &action
record.instance_exec(*args, &code)
true
when Array
code.each {|a| invoke_callbacks(a, record, args)}
true
else
false
end
end

## DSL interface
def transitions(trans_opts)
# Create a separate transition for each from state to the given state
Array(trans_opts[:from]).each do |s|
Expand All @@ -141,6 +114,13 @@ def transitions(trans_opts)
@transitions << AASM::SupportingClasses::StateTransition.new(trans_opts) if @transitions.empty? && trans_opts[:to]
end

[:after, :before, :error, :success].each do |callback_name|
define_method callback_name do |*args, &block|
options[callback_name] = Array(options[callback_name])
options[callback_name] << block if block
options[callback_name] += Array(args)
end
end
end
end # SupportingClasses
end # AASM
158 changes: 112 additions & 46 deletions spec/unit/supporting_classes/event_spec.rb
Expand Up @@ -3,6 +3,8 @@
describe 'adding an event' do
let(:event) do
AASM::SupportingClasses::Event.new(:close_order, {:success => :success_callback}) do
before :before_callback
after :after_callback
transitions :to => :closed, :from => [:open, :received]
end
end
Expand All @@ -12,7 +14,15 @@
end

it 'should set the success callback' do
event.success.should == :success_callback
event.options[:success].should == :success_callback
end

it 'should set the after callback' do
event.options[:after].should == [:after_callback]
end

it 'should set the before callback' do
event.options[:before].should == [:before_callback]
end

it 'should create transitions' do
Expand Down Expand Up @@ -82,69 +92,125 @@

end

describe 'executing the success callback' do
describe 'should fire callbacks' do
describe 'success' do
it "if it's a symbol" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_symbol, :success => :symbol_success_callback do
transitions :to => :symbol, :from => [:initial]
end
}

model = ThisNameBetterNotBeInUse.new
model.should_receive(:symbol_success_callback)
model.with_symbol!
end

it "should send the success callback if it's a symbol" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_symbol, :success => :symbol_success_callback do
transitions :to => :symbol, :from => [:initial]
end
}
it "if it's a string" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_string, :success => 'string_success_callback' do
transitions :to => :string, :from => [:initial]
end
}

model = ThisNameBetterNotBeInUse.new
model.should_receive(:symbol_success_callback)
model.with_symbol!
end
model = ThisNameBetterNotBeInUse.new
model.should_receive(:string_success_callback)
model.with_string!
end

it "should send the success callback if it's a string" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_string, :success => 'string_success_callback' do
transitions :to => :string, :from => [:initial]
end
}
it "if passed an array of strings and/or symbols" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_array, :success => [:success_callback1, 'success_callback2'] do
transitions :to => :array, :from => [:initial]
end
}

model = ThisNameBetterNotBeInUse.new
model.should_receive(:success_callback1)
model.should_receive(:success_callback2)
model.with_array!
end

model = ThisNameBetterNotBeInUse.new
model.should_receive(:string_success_callback)
model.with_string!
it "if passed an array of strings and/or symbols and/or procs" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_array_including_procs, :success => [:success_callback1, 'success_callback2', lambda { proc_success_callback }] do
transitions :to => :array, :from => [:initial]
end
}

model = ThisNameBetterNotBeInUse.new
model.should_receive(:success_callback1)
model.should_receive(:success_callback2)
model.should_receive(:proc_success_callback)
model.with_array_including_procs!
end

it "if it's a proc" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_proc, :success => lambda { proc_success_callback } do
transitions :to => :proc, :from => [:initial]
end
}

model = ThisNameBetterNotBeInUse.new
model.should_receive(:proc_success_callback)
model.with_proc!
end
end

it "should call each success callback if passed an array of strings and/or symbols" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_array, :success => [:success_callback1, 'success_callback2'] do
transitions :to => :array, :from => [:initial]
describe 'after' do
it "if they set different ways" do
ThisNameBetterNotBeInUse.instance_eval do
aasm_event :with_afters, :after => :do_one_thing_after do
after do
do_another_thing_after_too
end
after do
do_third_thing_at_last
end
transitions :to => :proc, :from => [:initial]
end
end
}

model = ThisNameBetterNotBeInUse.new
model.should_receive(:success_callback1)
model.should_receive(:success_callback2)
model.with_array!
model = ThisNameBetterNotBeInUse.new
model.should_receive(:do_one_thing_after).once.ordered
model.should_receive(:do_another_thing_after_too).once.ordered
model.should_receive(:do_third_thing_at_last).once.ordered
model.with_afters!
end
end

it "should call each success callback if passed an array of strings and/or symbols and/or procs" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_array_including_procs, :success => [:success_callback1, 'success_callback2', lambda { |obj| obj.proc_success_callback }] do
transitions :to => :array, :from => [:initial]
describe 'before' do
it "if it's a proc" do
ThisNameBetterNotBeInUse.instance_eval do
aasm_event :before_as_proc do
before do
do_something_before
end
transitions :to => :proc, :from => [:initial]
end
end
}

model = ThisNameBetterNotBeInUse.new
model.should_receive(:success_callback1)
model.should_receive(:success_callback2)
model.should_receive(:proc_success_callback)
model.with_array_including_procs!
model = ThisNameBetterNotBeInUse.new
model.should_receive(:do_something_before).once
model.before_as_proc!
end
end

it "should call the success callback if it's a proc" do
ThisNameBetterNotBeInUse.instance_eval {
aasm_event :with_proc, :success => lambda { |obj| obj.proc_success_callback } do
it 'in right order' do
ThisNameBetterNotBeInUse.instance_eval do
aasm_event :in_right_order, :after => :do_something_after do
before do
do_something_before
end
transitions :to => :proc, :from => [:initial]
end
}
end

model = ThisNameBetterNotBeInUse.new
model.should_receive(:proc_success_callback)
model.with_proc!
model.should_receive(:do_something_before).once.ordered
model.should_receive(:do_something_after).once.ordered
model.in_right_order!
end
end

Expand Down

0 comments on commit 6f9be86

Please sign in to comment.