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