Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/stiff/aasm into merging-s…
Browse files Browse the repository at this point in the history
…tiff-master

Conflicts:
	README.md
	lib/aasm.rb
	lib/aasm/event.rb
	lib/aasm/transition.rb
	spec/unit/transition_spec.rb
  • Loading branch information
alto committed Sep 9, 2014
2 parents 8e356d3 + 3de8aa3 commit 0edcc51
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 81 deletions.
40 changes: 22 additions & 18 deletions README.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/aasm.rb
Expand Up @@ -5,6 +5,7 @@
errors
configuration
base
dsl_helper
instance_base
transition
event
Expand Down
30 changes: 30 additions & 0 deletions lib/aasm/dsl_helper.rb
@@ -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
19 changes: 3 additions & 16 deletions lib/aasm/event.rb
@@ -1,13 +1,15 @@
module AASM
class Event
include DslHelper

attr_reader :name, :options

def initialize(name, options = {}, &block)
@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
Expand Down Expand Up @@ -82,14 +84,6 @@ def attach_event_guards(definitions)
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
Expand Down Expand Up @@ -141,12 +135,5 @@ def invoke_callbacks(code, record, args)
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
57 changes: 33 additions & 24 deletions lib/aasm/transition.rb
@@ -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)
Expand All @@ -40,15 +36,28 @@ def from?(value)

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

Expand Down
10 changes: 7 additions & 3 deletions spec/models/auth_machine.rb
Expand Up @@ -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
Expand All @@ -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

Expand Down
6 changes: 3 additions & 3 deletions spec/models/parametrised_event.rb
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions spec/unit/event_spec.rb
Expand Up @@ -262,24 +262,24 @@
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)
Expand Down

0 comments on commit 0edcc51

Please sign in to comment.