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:
commit
0edcc51756
9 changed files with 141 additions and 81 deletions
40
README.md
40
README.md
|
@ -94,8 +94,12 @@ class Job
|
|||
state :sleeping, :initial => true, :before_enter => :do_something
|
||||
state :running
|
||||
|
||||
event :run, :after => Proc.new { |user| notify_somebody(user) } do
|
||||
transitions :from => :sleeping, :to => :running, :on_transition => Proc.new {|obj, *args| obj.set_process(*args) }
|
||||
event :run, :after => :notify_somebody do
|
||||
transitions :from => :sleeping, :to => :running, :after => Proc.new {|*args| set_process(*args) } do
|
||||
before do
|
||||
log('Preparing to run')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
event :sleep do
|
||||
|
@ -141,8 +145,8 @@ begin
|
|||
new_state enter
|
||||
event guards
|
||||
transition guards
|
||||
transition on_transition
|
||||
...update state...
|
||||
transition after
|
||||
event success # if persist successful
|
||||
old_state after_exit
|
||||
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):
|
||||
|
||||
```ruby
|
||||
class Job
|
||||
class Cleaner
|
||||
include AASM
|
||||
|
||||
aasm do
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
state :idle, :initial => true
|
||||
state :cleaning
|
||||
|
||||
event :run do
|
||||
transitions :from => :sleeping, :to => :running
|
||||
end
|
||||
|
||||
event :clean do
|
||||
transitions :from => :running, :to => :cleaning
|
||||
transitions :from => :idle, :to => :cleaning, :guard => :cleaning_needed?
|
||||
end
|
||||
|
||||
event :sleep do
|
||||
transitions :from => :running, :to => :sleeping, :guard => :cleaning_needed?
|
||||
event :clean_if_needed do
|
||||
transitions :from => :idle, :to => :cleaning do
|
||||
guard do
|
||||
cleaning_needed?
|
||||
end
|
||||
end
|
||||
transitions :from => :idle, :to => :idle
|
||||
end
|
||||
end
|
||||
|
||||
def cleaning_needed?
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
job = Job.new
|
||||
job.run
|
||||
job.may_sleep? # => false
|
||||
job.sleep # => raises AASM::InvalidTransition
|
||||
job = Cleaner.new
|
||||
job.may_clean? # => false
|
||||
job.clean # => 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
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'ostruct'
|
|||
errors
|
||||
configuration
|
||||
base
|
||||
dsl_helper
|
||||
instance_base
|
||||
transition
|
||||
event
|
||||
|
|
30
lib/aasm/dsl_helper.rb
Normal file
30
lib/aasm/dsl_helper.rb
Normal 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
|
|
@ -1,5 +1,6 @@
|
|||
module AASM
|
||||
class Event
|
||||
include DslHelper
|
||||
|
||||
attr_reader :name, :options
|
||||
|
||||
|
@ -7,7 +8,8 @@ module AASM
|
|||
@name = name
|
||||
@transitions = []
|
||||
@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
|
||||
|
||||
# a neutered version of fire - it doesn't actually fire the event, it just
|
||||
|
@ -82,14 +84,6 @@ module AASM
|
|||
definitions
|
||||
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
|
||||
def _fire(obj, test, to_state=nil, *args)
|
||||
result = test ? false : nil
|
||||
|
@ -141,12 +135,5 @@ module AASM
|
|||
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 # AASM
|
||||
|
|
|
@ -1,33 +1,29 @@
|
|||
module AASM
|
||||
class Transition
|
||||
include DslHelper
|
||||
|
||||
attr_reader :from, :to, :opts
|
||||
alias_method :options, :opts
|
||||
|
||||
def initialize(opts)
|
||||
@from = opts[:from]
|
||||
@to = opts[:to]
|
||||
@guards = Array(opts[:guard] || opts[:guards])
|
||||
@on_transition = opts[:on_transition]
|
||||
def initialize(opts, &block)
|
||||
add_options_from_dsl(opts, [:on_transition, :guard, :after], &block) if block
|
||||
|
||||
@from, @to, @guards = opts[:from], opts[:to], Array(opts[:guard] || opts[:guards])
|
||||
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
|
||||
end
|
||||
|
||||
# TODO: should be named allowed? or similar
|
||||
def perform(obj, *args)
|
||||
@guards.each do |guard|
|
||||
case guard
|
||||
when Symbol, String
|
||||
return false unless obj.send(guard, *args)
|
||||
when Proc
|
||||
return false unless guard.call(obj, *args)
|
||||
end
|
||||
end
|
||||
true
|
||||
invoke_callbacks_compatible_with_guard(@guards, obj, args, :guard => true)
|
||||
end
|
||||
|
||||
def execute(obj, *args)
|
||||
@on_transition.is_a?(Array) ?
|
||||
@on_transition.each {|ot| _execute(obj, ot, *args)} :
|
||||
_execute(obj, @on_transition, *args)
|
||||
invoke_callbacks_compatible_with_guard(@after, obj, args)
|
||||
end
|
||||
|
||||
def ==(obj)
|
||||
|
@ -40,15 +36,28 @@ module AASM
|
|||
|
||||
private
|
||||
|
||||
def _execute(obj, on_transition, *args)
|
||||
obj.aasm.from_state = @from if obj.aasm.respond_to?(:from_state=)
|
||||
obj.aasm.to_state = @to if obj.aasm.respond_to?(:to_state=)
|
||||
def invoke_callbacks_compatible_with_guard(code, record, args, options={})
|
||||
if record.respond_to?(:aasm)
|
||||
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
|
||||
when Proc
|
||||
on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
|
||||
case code
|
||||
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
|
||||
|
||||
|
|
|
@ -12,7 +12,11 @@ class AuthMachine
|
|||
state :waiting
|
||||
|
||||
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
|
||||
|
||||
event :activate do
|
||||
|
@ -33,8 +37,8 @@ class AuthMachine
|
|||
end
|
||||
|
||||
event :unsuspend do
|
||||
transitions :from => :suspended, :to => :active, :guard => Proc.new {|u| u.has_activated? }
|
||||
transitions :from => :suspended, :to => :pending, :guard => Proc.new {|u| u.has_activation_code? }
|
||||
transitions :from => :suspended, :to => :active, :guard => Proc.new { has_activated? }
|
||||
transitions :from => :suspended, :to => :pending, :guard => :has_activation_code?
|
||||
transitions :from => :suspended, :to => :passive
|
||||
end
|
||||
|
||||
|
|
|
@ -12,9 +12,9 @@ class ParametrisedEvent
|
|||
end
|
||||
|
||||
event :dress do
|
||||
transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
|
||||
transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
|
||||
transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair]
|
||||
transitions :from => :sleeping, :to => :working, :after => :wear_clothes
|
||||
transitions :from => :showering, :to => [:working, :dating], :after => Proc.new { |*args| wear_clothes(*args) }
|
||||
transitions :from => :showering, :to => :prettying_up, :after => [:condition_hair, :fix_hair]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -262,24 +262,24 @@ describe 'parametrised events' do
|
|||
expect(pe.aasm.current_state).to eq(:showering)
|
||||
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')
|
||||
expect(pe.aasm.current_state).to eq(:working)
|
||||
end
|
||||
|
||||
it 'should call on_transition method with args' do
|
||||
it 'should call :after transition method with args' do
|
||||
pe.wakeup!(:showering)
|
||||
expect(pe).to receive(:wear_clothes).with('blue', 'jeans')
|
||||
pe.dress!(:working, 'blue', 'jeans')
|
||||
end
|
||||
|
||||
it 'should call on_transition proc' do
|
||||
it 'should call :after transition proc' do
|
||||
pe.wakeup!(:showering)
|
||||
expect(pe).to receive(:wear_clothes).with('purple', 'slacks')
|
||||
pe.dress!(:dating, 'purple', 'slacks')
|
||||
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)
|
||||
expect(pe).to receive(:condition_hair)
|
||||
expect(pe).to receive(:fix_hair)
|
||||
|
|
|
@ -61,6 +61,30 @@ describe AASM::Transition do
|
|||
expect(st.opts).to eq(opts)
|
||||
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
|
||||
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
||||
st = AASM::Transition.new(opts)
|
||||
|
@ -124,7 +148,7 @@ describe AASM::Transition, '- when performing guard checks' do
|
|||
end
|
||||
|
||||
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)
|
||||
|
||||
obj = double('object')
|
||||
|
@ -136,31 +160,32 @@ end
|
|||
|
||||
describe AASM::Transition, '- when executing the transition with a Proc' 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)
|
||||
args = {:arg1 => '1', :arg2 => '2'}
|
||||
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)
|
||||
end
|
||||
|
||||
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)
|
||||
args = {:arg1 => '1', :arg2 => '2'}
|
||||
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)
|
||||
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
|
||||
opts = {:from => 'foo', :to => 'bar', :on_transition => 'test'}
|
||||
opts = {:from => 'foo', :to => 'bar', :after => 'test'}
|
||||
st = AASM::Transition.new(opts)
|
||||
args = {:arg1 => '1', :arg2 => '2'}
|
||||
obj = double('object', :aasm => 'aasm')
|
||||
|
@ -171,7 +196,7 @@ describe AASM::Transition, '- when executing the transition with an :on_transtio
|
|||
end
|
||||
|
||||
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)
|
||||
args = {:arg1 => '1', :arg2 => '2'}
|
||||
obj = double('object', :aasm => 'aasm')
|
||||
|
@ -182,7 +207,7 @@ describe AASM::Transition, '- when executing the transition with an :on_transtio
|
|||
end
|
||||
|
||||
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)
|
||||
args = {:arg1 => '1', :arg2 => '2'}
|
||||
obj = double('object', :aasm => 'aasm')
|
||||
|
@ -197,7 +222,7 @@ describe AASM::Transition, '- when executing the transition with an :on_transtio
|
|||
end
|
||||
|
||||
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)
|
||||
args = {:arg1 => '1', :arg2 => '2'}
|
||||
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
|
||||
opts = {:from => 'foo', :to => 'bar', :on_transition => :test}
|
||||
st = AASM::Transition.new(opts)
|
||||
transition = AASM::Transition.new(opts)
|
||||
args = {:arg1 => '1', :arg2 => '2'}
|
||||
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}"
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue