aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib
diff options
context:
space:
mode:
authorrick <technoweenie@gmail.com>2008-06-28 09:19:44 -0700
committerrick <technoweenie@gmail.com>2008-06-28 09:19:44 -0700
commit74cb05698684f237a7eb91afadec0020d8910c70 (patch)
treeff961d9f9c2119a6608db90bc41d713a191a9b9f /activemodel/lib
parentb9528ad3c5379896b00772cb44faf1db0fd882d7 (diff)
downloadrails-74cb05698684f237a7eb91afadec0020d8910c70.tar.gz
rails-74cb05698684f237a7eb91afadec0020d8910c70.tar.bz2
rails-74cb05698684f237a7eb91afadec0020d8910c70.zip
add basic events and transitions. still more tests to convert
Diffstat (limited to 'activemodel/lib')
-rw-r--r--activemodel/lib/active_model/state_machine.rb11
-rw-r--r--activemodel/lib/active_model/state_machine/event.rb67
-rw-r--r--activemodel/lib/active_model/state_machine/machine.rb44
-rw-r--r--activemodel/lib/active_model/state_machine/state.rb18
-rw-r--r--activemodel/lib/active_model/state_machine/state_transition.rb40
5 files changed, 162 insertions, 18 deletions
diff --git a/activemodel/lib/active_model/state_machine.rb b/activemodel/lib/active_model/state_machine.rb
index bb038f6b7a..2a5ac95a3e 100644
--- a/activemodel/lib/active_model/state_machine.rb
+++ b/activemodel/lib/active_model/state_machine.rb
@@ -5,6 +5,9 @@ end
module ActiveModel
module StateMachine
+ class InvalidTransition < Exception
+ end
+
def self.included(base)
base.extend ClassMethods
end
@@ -34,10 +37,14 @@ module ActiveModel
end
end
- def current_state(name = nil)
+ def current_state(name = nil, new_state = nil)
sm = self.class.state_machine(name)
ivar = "@#{sm.name}_current_state"
- instance_variable_get(ivar) || instance_variable_set(ivar, sm.initial_state)
+ if name && new_state
+ instance_variable_set(ivar, new_state)
+ else
+ instance_variable_get(ivar) || instance_variable_set(ivar, sm.initial_state)
+ end
end
end
end \ No newline at end of file
diff --git a/activemodel/lib/active_model/state_machine/event.rb b/activemodel/lib/active_model/state_machine/event.rb
new file mode 100644
index 0000000000..cc7d563214
--- /dev/null
+++ b/activemodel/lib/active_model/state_machine/event.rb
@@ -0,0 +1,67 @@
+module ActiveModel
+ module StateMachine
+ class Event
+ attr_reader :name, :success
+
+ def initialize(name, options = {}, &block)
+ @name, @transitions = name, []
+ machine = options.delete(:machine)
+ if machine
+ machine.klass.send(:define_method, "#{name.to_s}!") do |*args|
+ machine.fire_event(name, self, true, *args)
+ end
+
+ machine.klass.send(:define_method, "#{name.to_s}") do |*args|
+ machine.fire_event(name, self, false, *args)
+ end
+ end
+ update(options, &block)
+ end
+
+ def fire(obj, to_state = nil, *args)
+ transitions = @transitions.select { |t| t.from == obj.current_state }
+ raise InvalidTransition if transitions.size == 0
+
+ next_state = nil
+ transitions.each do |transition|
+ next if to_state && !Array(transition.to).include?(to_state)
+ if transition.perform(obj)
+ next_state = to_state || Array(transition.to).first
+ transition.execute(obj, *args)
+ break
+ end
+ end
+ next_state
+ end
+
+ def transitions_from_state?(state)
+ @transitions.any? { |t| t.from? state }
+ end
+
+ def success?
+ !!@success
+ end
+
+ def ==(event)
+ if event.is_a? Symbol
+ name == event
+ else
+ name == event.name
+ end
+ end
+
+ def update(options = {}, &block)
+ if options.key?(:success) then @success = options[:success] end
+ if block then instance_eval(&block) end
+ self
+ end
+
+ private
+ def transitions(trans_opts)
+ Array(trans_opts[:from]).each do |s|
+ @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
+ end
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/state_machine/machine.rb b/activemodel/lib/active_model/state_machine/machine.rb
index 75ed8f8b65..1da48290c5 100644
--- a/activemodel/lib/active_model/state_machine/machine.rb
+++ b/activemodel/lib/active_model/state_machine/machine.rb
@@ -1,29 +1,49 @@
module ActiveModel
module StateMachine
class Machine
- attr_accessor :initial_state, :states, :event
+ attr_accessor :initial_state, :states, :events, :state_index
attr_reader :klass, :name
- def initialize(klass, name)
- @klass, @name, @states, @events = klass, name, [], {}
+ def initialize(klass, name, options = {}, &block)
+ @klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
+ update(options, &block)
+ end
+
+ def initial_state
+ @initial_state ||= (states.first ? states.first.name : nil)
+ end
+
+ def update(options = {}, &block)
+ if options.key?(:initial) then @initial_state = options[:initial] end
+ if block then instance_eval(&block) end
+ self
+ end
+
+ def fire_event(name, record, persist, *args)
+ state_index[record.current_state].call_action(:exit, record)
+ if new_state = @events[name].fire(record, *args)
+ state_index[new_state].call_action(:enter, record)
+ record.current_state(@name, new_state)
+ else
+ false
+ end
end
def states_for_select
states.map { |st| [st.display_name, st.name.to_s] }
end
- def state(name, options = {})
- @states << State.new(self, name, options)
+ def events_for(state)
+ events = @events.values.select { |event| event.transitions_from_state?(state) }
+ events.map! { |event| event.name }
end
-
- def initial_state
- @initial_state ||= (states.first ? states.first.name : nil)
+ private
+ def state(name, options = {})
+ @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
end
- def update(options = {}, &block)
- @initial_state = options[:initial]
- instance_eval(&block)
- self
+ def event(name, options = {}, &block)
+ (@events[name] ||= Event.new(name, :machine => self)).update(options, &block)
end
end
end
diff --git a/activemodel/lib/active_model/state_machine/state.rb b/activemodel/lib/active_model/state_machine/state.rb
index 5851a6fb79..68eb2aa34a 100644
--- a/activemodel/lib/active_model/state_machine/state.rb
+++ b/activemodel/lib/active_model/state_machine/state.rb
@@ -3,11 +3,15 @@ module ActiveModel
class State
attr_reader :name, :options
- def initialize(machine, name, options={})
- @machine, @name, @options, @display_name = machine, name, options, options.delete(:display)
- machine.klass.send(:define_method, "#{name}?") do
- current_state.to_s == name.to_s
+ def initialize(name, options = {})
+ @name = name
+ machine = options.delete(:machine)
+ if machine
+ machine.klass.send(:define_method, "#{name}?") do
+ current_state.to_s == name.to_s
+ end
end
+ update(options)
end
def ==(state)
@@ -35,6 +39,12 @@ module ActiveModel
def for_select
[display_name, name.to_s]
end
+
+ def update(options = {})
+ if options.key?(:display) then @display_name = options.delete(:display) end
+ @options = options
+ self
+ end
end
end
end
diff --git a/activemodel/lib/active_model/state_machine/state_transition.rb b/activemodel/lib/active_model/state_machine/state_transition.rb
new file mode 100644
index 0000000000..f9df998ea4
--- /dev/null
+++ b/activemodel/lib/active_model/state_machine/state_transition.rb
@@ -0,0 +1,40 @@
+module ActiveModel
+ module StateMachine
+ class StateTransition
+ attr_reader :from, :to, :options
+
+ def initialize(opts)
+ @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
+ @options = opts
+ end
+
+ def perform(obj)
+ case @guard
+ when Symbol, String
+ obj.send(@guard)
+ when Proc
+ @guard.call(obj)
+ else
+ true
+ end
+ end
+
+ def execute(obj, *args)
+ case @on_transition
+ when Symbol, String
+ obj.send(@on_transition, *args)
+ when Proc
+ @on_transition.call(obj, *args)
+ end
+ end
+
+ def ==(obj)
+ @from == obj.from && @to == obj.to
+ end
+
+ def from?(value)
+ @from == value
+ end
+ end
+ end
+end