In an introductory chapter about DSL, Martin Fowler mentions the state machine model. Now here is how you can implement a simple finite state machine. A FSM DSL can be defined for example like this (if we assign states, the first state is used as an initial value):

class StateMachineDSL
  attr_accessor :state

  def initialize
    @states, @events = [], {}
  end

  def states
    @states
  end

  def states=(values)
    @states, @state = values, values.first
  end

  def event(name, transition)
    @events[name] = transition            
    self.class.send(:define_method,"#{name}!") { change_state(name) }
  end

  def transitions
    @events.reject{ |key,value| !value.keys.include?(@state.to_sym) }.keys
  end

  def change_state(event)
    @state = @events[event][@state]
  end
end

If we use the block technique mentioned before to apply the DSL in Ruby, we get the following class:

class MyClass
  def self.apply_dsl(a_class, &block)
    obj = a_class.new
    yield obj if block_given?
    return obj
  end
end

Finally, the FSM can be defined in the DSL:

obj = MyClass.apply_dsl(StateMachineDSL) do |state_machine|
  state_machine.states = [:submitted, :reviewing, :accepted, :rejected]
  state_machine.event :submit, {:submitted => :reviewing}
  state_machine.event :accept, {:reviewing => :accepted}
  state_machine.event :reject, {:reviewing => :rejected}
  state_machine.event :revise, {:rejected => :reviewing}
end

obj.state
=> :submitted
obj.submit!
=> :reviewing
obj.accept!
=> :accepted
Advertisement