diff options
Diffstat (limited to 'activemodel/lib/active_model/state_machine')
4 files changed, 153 insertions, 16 deletions
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 |