1
0
Fork 0
mirror of https://github.com/aasm/aasm synced 2023-03-27 23:22:41 -04:00

Merge branch 'master' of https://github.com/stiff/aasm into merging-stiff-master

Conflicts:
	README.md
	lib/aasm.rb
	lib/aasm/event.rb
	lib/aasm/transition.rb
	spec/unit/transition_spec.rb
This commit is contained in:
Thorsten Böttger 2014-09-10 00:22:28 +02:00
commit 0edcc51756
9 changed files with 141 additions and 81 deletions

View file

@ -94,8 +94,12 @@ class Job
state :sleeping, :initial => true, :before_enter => :do_something state :sleeping, :initial => true, :before_enter => :do_something
state :running state :running
event :run, :after => Proc.new { |user| notify_somebody(user) } do event :run, :after => :notify_somebody do
transitions :from => :sleeping, :to => :running, :on_transition => Proc.new {|obj, *args| obj.set_process(*args) } transitions :from => :sleeping, :to => :running, :after => Proc.new {|*args| set_process(*args) } do
before do
log('Preparing to run')
end
end
end end
event :sleep do event :sleep do
@ -141,8 +145,8 @@ begin
new_state enter new_state enter
event guards event guards
transition guards transition guards
transition on_transition
...update state... ...update state...
transition after
event success # if persist successful event success # if persist successful
old_state after_exit old_state after_exit
new_state after_enter new_state after_enter
@ -183,37 +187,37 @@ running the transition. If the guard returns `false` the transition will be
denied (raising `AASM::InvalidTransition` or returning `false` itself): denied (raising `AASM::InvalidTransition` or returning `false` itself):
```ruby ```ruby
class Job class Cleaner
include AASM include AASM
aasm do aasm do
state :sleeping, :initial => true state :idle, :initial => true
state :running
state :cleaning state :cleaning
event :run do
transitions :from => :sleeping, :to => :running
end
event :clean do event :clean do
transitions :from => :running, :to => :cleaning transitions :from => :idle, :to => :cleaning, :guard => :cleaning_needed?
end end
event :sleep do event :clean_if_needed do
transitions :from => :running, :to => :sleeping, :guard => :cleaning_needed? transitions :from => :idle, :to => :cleaning do
guard do
cleaning_needed?
end
end
transitions :from => :idle, :to => :idle
end end
end end
def cleaning_needed? def cleaning_needed?
false false
end end
end end
job = Job.new job = Cleaner.new
job.run job.may_clean? # => false
job.may_sleep? # => false job.clean # => raises AASM::InvalidTransition
job.sleep # => raises AASM::InvalidTransition job.may_clean_if_needed? # => true
job.clean_if_needed! # idle
``` ```
You can even provide a number of guards, which all have to succeed to proceed You can even provide a number of guards, which all have to succeed to proceed

View file

@ -5,6 +5,7 @@ require 'ostruct'
errors errors
configuration configuration
base base
dsl_helper
instance_base instance_base
transition transition
event event

30
lib/aasm/dsl_helper.rb Normal file
View file

@ -0,0 +1,30 @@
module DslHelper
class Proxy
attr_accessor :options
def initialize(options, valid_keys, source)
@valid_keys = valid_keys
@source = source
@options = options
end
def method_missing(name, *args, &block)
if @valid_keys.include?(name)
options[name] = Array(options[name])
options[name] << block if block
options[name] += Array(args)
else
@source.send name, *args, &block
end
end
end
def add_options_from_dsl(options, valid_keys, &block)
proxy = Proxy.new(options, valid_keys, self)
proxy.instance_eval(&block)
proxy.options
end
end

View file

@ -1,5 +1,6 @@
module AASM module AASM
class Event class Event
include DslHelper
attr_reader :name, :options attr_reader :name, :options
@ -7,7 +8,8 @@ module AASM
@name = name @name = name
@transitions = [] @transitions = []
@guards = Array(options[:guard] || options[:guards]) @guards = Array(options[:guard] || options[:guards])
update(options, &block) @options = options # QUESTION: .dup ?
add_options_from_dsl(@options, [:after, :before, :error, :success], &block) if block
end end
# a neutered version of fire - it doesn't actually fire the event, it just # a neutered version of fire - it doesn't actually fire the event, it just
@ -82,14 +84,6 @@ module AASM
definitions definitions
end end
def update(options = {}, &block)
@options = options
if block then
instance_eval(&block)
end
self
end
# Execute if test == false, otherwise return true/false depending on whether it would fire # Execute if test == false, otherwise return true/false depending on whether it would fire
def _fire(obj, test, to_state=nil, *args) def _fire(obj, test, to_state=nil, *args)
result = test ? false : nil result = test ? false : nil
@ -141,12 +135,5 @@ module AASM
end end
end 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
end # AASM end # AASM

View file

@ -1,33 +1,29 @@
module AASM module AASM
class Transition class Transition
include DslHelper
attr_reader :from, :to, :opts attr_reader :from, :to, :opts
alias_method :options, :opts alias_method :options, :opts
def initialize(opts) def initialize(opts, &block)
@from = opts[:from] add_options_from_dsl(opts, [:on_transition, :guard, :after], &block) if block
@to = opts[:to]
@guards = Array(opts[:guard] || opts[:guards]) @from, @to, @guards = opts[:from], opts[:to], Array(opts[:guard] || opts[:guards])
@on_transition = opts[:on_transition] if opts[:on_transition]
warn '[DEPRECATION] :on_transition is deprecated, use :after instead'
opts[:after] = Array(opts[:after]) + Array(opts[:on_transition])
end
@after = opts[:after]
@opts = opts @opts = opts
end end
# TODO: should be named allowed? or similar # TODO: should be named allowed? or similar
def perform(obj, *args) def perform(obj, *args)
@guards.each do |guard| invoke_callbacks_compatible_with_guard(@guards, obj, args, :guard => true)
case guard
when Symbol, String
return false unless obj.send(guard, *args)
when Proc
return false unless guard.call(obj, *args)
end
end
true
end end
def execute(obj, *args) def execute(obj, *args)
@on_transition.is_a?(Array) ? invoke_callbacks_compatible_with_guard(@after, obj, args)
@on_transition.each {|ot| _execute(obj, ot, *args)} :
_execute(obj, @on_transition, *args)
end end
def ==(obj) def ==(obj)
@ -40,15 +36,28 @@ module AASM
private private
def _execute(obj, on_transition, *args) def invoke_callbacks_compatible_with_guard(code, record, args, options={})
obj.aasm.from_state = @from if obj.aasm.respond_to?(:from_state=) if record.respond_to?(:aasm)
obj.aasm.to_state = @to if obj.aasm.respond_to?(:to_state=) record.aasm.from_state = @from if record.aasm.respond_to?(:from_state=)
record.aasm.to_state = @to if record.aasm.respond_to?(:to_state=)
end
case on_transition case code
when Proc
on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
when Symbol, String when Symbol, String
obj.send(:method, on_transition.to_sym).arity == 0 ? obj.send(on_transition) : obj.send(on_transition, *args) # QUESTION : record.send(code, *args) ?
arity = record.send(:method, code.to_sym).arity
arity == 0 ? record.send(code) : record.send(code, *args)
when Proc
# QUESTION : record.instance_exec(*args, &code) ?
code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code)
when Array
if options[:guard] # guard callbacks
code.all? {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
else # after callbacks
code.map {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
end
else
true
end end
end end

View file

@ -12,7 +12,11 @@ class AuthMachine
state :waiting state :waiting
event :register do event :register do
transitions :from => :passive, :to => :pending, :guard => Proc.new {|u| u.can_register? } transitions :from => :passive, :to => :pending do
guard do
can_register?
end
end
end end
event :activate do event :activate do
@ -33,8 +37,8 @@ class AuthMachine
end end
event :unsuspend do event :unsuspend do
transitions :from => :suspended, :to => :active, :guard => Proc.new {|u| u.has_activated? } transitions :from => :suspended, :to => :active, :guard => Proc.new { has_activated? }
transitions :from => :suspended, :to => :pending, :guard => Proc.new {|u| u.has_activation_code? } transitions :from => :suspended, :to => :pending, :guard => :has_activation_code?
transitions :from => :suspended, :to => :passive transitions :from => :suspended, :to => :passive
end end

View file

@ -12,9 +12,9 @@ class ParametrisedEvent
end end
event :dress do event :dress do
transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes transitions :from => :sleeping, :to => :working, :after => :wear_clothes
transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) } transitions :from => :showering, :to => [:working, :dating], :after => Proc.new { |*args| wear_clothes(*args) }
transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair] transitions :from => :showering, :to => :prettying_up, :after => [:condition_hair, :fix_hair]
end end
end end

View file

@ -262,24 +262,24 @@ describe 'parametrised events' do
expect(pe.aasm.current_state).to eq(:showering) expect(pe.aasm.current_state).to eq(:showering)
end end
it 'should transition to default state when on_transition invoked' do it 'should transition to default state when :after transition invoked' do
pe.dress!(nil, 'purple', 'dressy') pe.dress!(nil, 'purple', 'dressy')
expect(pe.aasm.current_state).to eq(:working) expect(pe.aasm.current_state).to eq(:working)
end end
it 'should call on_transition method with args' do it 'should call :after transition method with args' do
pe.wakeup!(:showering) pe.wakeup!(:showering)
expect(pe).to receive(:wear_clothes).with('blue', 'jeans') expect(pe).to receive(:wear_clothes).with('blue', 'jeans')
pe.dress!(:working, 'blue', 'jeans') pe.dress!(:working, 'blue', 'jeans')
end end
it 'should call on_transition proc' do it 'should call :after transition proc' do
pe.wakeup!(:showering) pe.wakeup!(:showering)
expect(pe).to receive(:wear_clothes).with('purple', 'slacks') expect(pe).to receive(:wear_clothes).with('purple', 'slacks')
pe.dress!(:dating, 'purple', 'slacks') pe.dress!(:dating, 'purple', 'slacks')
end end
it 'should call on_transition with an array of methods' do it 'should call :after transition with an array of methods' do
pe.wakeup!(:showering) pe.wakeup!(:showering)
expect(pe).to receive(:condition_hair) expect(pe).to receive(:condition_hair)
expect(pe).to receive(:fix_hair) expect(pe).to receive(:fix_hair)

View file

@ -61,6 +61,30 @@ describe AASM::Transition do
expect(st.opts).to eq(opts) expect(st.opts).to eq(opts)
end end
it 'should set on_transition with deprecation warning' do
opts = {:from => 'foo', :to => 'bar'}
st = AASM::Transition.allocate
st.should_receive(:warn).with('[DEPRECATION] :on_transition is deprecated, use :after instead')
st.send :initialize, opts do
guard :gg
on_transition :after_callback
end
st.opts[:after].should == [:after_callback]
end
it 'should set after and guard from dsl' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = AASM::Transition.new(opts) do
guard :gg
after :after_callback
end
st.opts[:guard].should == ['g', :gg]
st.opts[:after].should == [:after_callback] # TODO fix this bad code coupling
end
it 'should pass equality check if from and to are the same' do it 'should pass equality check if from and to are the same' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'} opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = AASM::Transition.new(opts) st = AASM::Transition.new(opts)
@ -124,7 +148,7 @@ describe AASM::Transition, '- when performing guard checks' do
end end
it 'should call the proc passing the object if the guard is a proc' do it 'should call the proc passing the object if the guard is a proc' do
opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test}} opts = {:from => 'foo', :to => 'bar', :guard => Proc.new { test }}
st = AASM::Transition.new(opts) st = AASM::Transition.new(opts)
obj = double('object') obj = double('object')
@ -136,31 +160,32 @@ end
describe AASM::Transition, '- when executing the transition with a Proc' do describe AASM::Transition, '- when executing the transition with a Proc' do
it 'should call a Proc on the object with args' do it 'should call a Proc on the object with args' do
opts = {:from => 'foo', :to => 'bar', :on_transition => Proc.new {|o| o.test}} opts = {:from => 'foo', :to => 'bar', :after => Proc.new {|a| test(a) }}
st = AASM::Transition.new(opts) st = AASM::Transition.new(opts)
args = {:arg1 => '1', :arg2 => '2'} args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm') obj = double('object', :aasm => 'aasm')
expect(opts[:on_transition]).to receive(:call).with(any_args) obj.should_receive(:test).with(args)
st.execute(obj, args) st.execute(obj, args)
end end
it 'should call a Proc on the object without args' do it 'should call a Proc on the object without args' do
opts = {:from => 'foo', :to => 'bar', :on_transition => Proc.new {||}} prc = Proc.new {||}
opts = {:from => 'foo', :to => 'bar', :after => prc }
st = AASM::Transition.new(opts) st = AASM::Transition.new(opts)
args = {:arg1 => '1', :arg2 => '2'} args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm') obj = double('object', :aasm => 'aasm')
expect(opts[:on_transition]).to receive(:call).with(no_args) obj.should_receive(:instance_exec).with(no_args) # FIXME bad spec
st.execute(obj, args) st.execute(obj, args)
end end
end end
describe AASM::Transition, '- when executing the transition with an :on_transtion method call' do describe AASM::Transition, '- when executing the transition with an :after method call' do
it 'should accept a String for the method name' do it 'should accept a String for the method name' do
opts = {:from => 'foo', :to => 'bar', :on_transition => 'test'} opts = {:from => 'foo', :to => 'bar', :after => 'test'}
st = AASM::Transition.new(opts) st = AASM::Transition.new(opts)
args = {:arg1 => '1', :arg2 => '2'} args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm') obj = double('object', :aasm => 'aasm')
@ -171,7 +196,7 @@ describe AASM::Transition, '- when executing the transition with an :on_transtio
end end
it 'should accept a Symbol for the method name' do it 'should accept a Symbol for the method name' do
opts = {:from => 'foo', :to => 'bar', :on_transition => :test} opts = {:from => 'foo', :to => 'bar', :after => :test}
st = AASM::Transition.new(opts) st = AASM::Transition.new(opts)
args = {:arg1 => '1', :arg2 => '2'} args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm') obj = double('object', :aasm => 'aasm')
@ -182,7 +207,7 @@ describe AASM::Transition, '- when executing the transition with an :on_transtio
end end
it 'should pass args if the target method accepts them' do it 'should pass args if the target method accepts them' do
opts = {:from => 'foo', :to => 'bar', :on_transition => :test} opts = {:from => 'foo', :to => 'bar', :after => :test}
st = AASM::Transition.new(opts) st = AASM::Transition.new(opts)
args = {:arg1 => '1', :arg2 => '2'} args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm') obj = double('object', :aasm => 'aasm')
@ -197,7 +222,7 @@ describe AASM::Transition, '- when executing the transition with an :on_transtio
end end
it 'should NOT pass args if the target method does NOT accept them' do it 'should NOT pass args if the target method does NOT accept them' do
opts = {:from => 'foo', :to => 'bar', :on_transition => :test} opts = {:from => 'foo', :to => 'bar', :after => :test}
st = AASM::Transition.new(opts) st = AASM::Transition.new(opts)
args = {:arg1 => '1', :arg2 => '2'} args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm') obj = double('object', :aasm => 'aasm')
@ -213,7 +238,7 @@ describe AASM::Transition, '- when executing the transition with an :on_transtio
it 'should allow accessing the from_state and the to_state' do it 'should allow accessing the from_state and the to_state' do
opts = {:from => 'foo', :to => 'bar', :on_transition => :test} opts = {:from => 'foo', :to => 'bar', :on_transition => :test}
st = AASM::Transition.new(opts) transition = AASM::Transition.new(opts)
args = {:arg1 => '1', :arg2 => '2'} args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => AASM::InstanceBase.new('object')) obj = double('object', :aasm => AASM::InstanceBase.new('object'))
@ -221,9 +246,9 @@ describe AASM::Transition, '- when executing the transition with an :on_transtio
"from: #{aasm.from_state} to: #{aasm.to_state}" "from: #{aasm.from_state} to: #{aasm.to_state}"
end end
return_value = st.execute(obj, args) return_value = transition.execute(obj, args)
expect(return_value).to eq('from: foo to: bar') expect(return_value).to eq(['from: foo to: bar'])
end end
end end