lib/finite/machine.rb in finite-0.0.1 vs lib/finite/machine.rb in finite-1.0.0
- old
+ new
@@ -1,5 +1,121 @@
module Finite
- class Machine
- # some code
+ # The State Machine class. Represents the whole state machine
+ class StateMachine
+
+ class << self
+ attr_accessor :machines
+ end
+
+ attr_reader :states, :initial, :events, :callbacks
+
+ # Create a new state machine
+ #
+ # @param initial_state [Symbol] the initial state of this state machine
+ # @param klass [Class] the class of the state machine
+ # @param block [Block] the block of code that creates it
+ def initialize(initial_state, klass, &block)
+ @class = klass
+ @initial = initial_state
+ @states = Hash.new
+ @events = Hash.new
+ @callbacks = {before: Hash.new , after: Hash.new}
+ instance_eval &block
+ end
+
+ # Add an event to the state machine
+ #
+ # @param event_name [Symbol] the event you are trying to add
+ # @param block [Block] the block of code that creates an event
+ # @raise [Exception] if the event already exists
+ def add_event(event_name, &block)
+ # We don't want to randomly override things that we shouldn't
+ raise "Method already taken can_#{event_name}?" if @class.methods.include?(:"can_#{event_name}?")
+ raise "Method already taken #{event_name}" if @class.methods.include?(:"#{event_name}")
+ raise 'Event #{event_name} already exists. Rename or combine the events' if events.include? event_name
+ event = Event.new(event_name, &block)
+ @events[event_name] = event
+ event.transitions.each_value do |transition|
+ add_state transition.to
+ add_state transition.from
+ end
+ @class.send(:define_method, :"can_#{event_name}?") do
+ event.transitions.key? current_state.name
+ end
+ @class.send(:define_method, :"#{event_name}") do
+ if event.transitions.key? current_state.name
+
+ event.callbacks[:before].each do |callback|
+ self.instance_eval &callback
+ end
+
+ if callbacks[:before].key? :all
+ callbacks[:before][:all].each do |callback|
+ self.instance_eval &callback
+ end
+ end
+
+ new_state = states[event.transitions[current_state.name].to]
+
+ if callbacks[:before].key? new_state.name
+ callbacks[:before][new_state.name].each do |callback|
+ self.instance_eval &callback
+ end
+ end
+ @current_state = new_state
+
+ if callbacks[:after].key? :all
+ callbacks[:after][:all].each do |callback|
+ self.instance_eval &callback
+ end
+ end
+ if callbacks[:after].key? current_state.name
+ callbacks[:after][current_state.name].each do |callback|
+ self.instance_eval &callback
+ end
+ end
+
+ event.callbacks[:after].each do |callback|
+ self.instance_eval &callback
+ end
+ else
+ raise 'Invalid Transition'
+ end
+ end
+ end
+
+ # Add a state to the the state machine if the state hasn't already been
+ # created
+ #
+ # @param state [Symbol] the state you are trying to add
+ def add_state(state)
+ if not @states.include? state
+ # Prevents arbitrarily overriding methods that you shouldn't be
+ raise "Method already taken #{state}?" if @class.methods.include?(:"#{state}?")
+ @states[state] = State.new(state)
+ @class.send(:define_method, :"#{state}?"){current_state == state}
+ end
+ end
+
+ private
+ # The event method for the dsl
+ #
+ # @param name [Symbol] the name of the event
+ # @param block [Block] the block of code that creates events
+ def event(name, &block)
+ add_event name, &block
+ end
+
+ # Create the callback methods
+ [:after, :before].each do |callback|
+ define_method callback do |*args, &block|
+ if args.count > 0
+ callbacks[callback][args[0]] ||= Array.new
+ callbacks[callback][args[0]] << block
+ else
+ callbacks[callback][:all] ||= Array.new
+ callbacks[callback][:all] << block
+ end
+ end
+ end
end
end
\ No newline at end of file