module StatefulEnum class Machine def initialize(model, column, states, &block) @model, @column, @states, @event_names = model, column, states, [] # undef non-verb methods e.g. Model#active! states.each_key do |state| @model.send :undef_method, "#{state}!" end instance_eval(&block) if block end def event(name, &block) raise "event: :#{name} has already been defined." if @event_names.include? name Event.new @model, @column, @states, name, &block @event_names << name end class Event def initialize(model, column, states, name, &block) @model, @column, @states, @name, @transitions = model, column, states, name, {} instance_eval(&block) if block define_transition_methods end def define_transition_methods column, name, transitions = @column, @name, @transitions @model.send(:define_method, name) do if (to = transitions[self.send(column).to_sym]) self.class.instance_variable_get(:@_enum_methods_module).instance_method("#{to}!").bind(self).call else false end end @model.send(:define_method, "#{name}!") do send(name) || raise('Invalid transition') end @model.send(:define_method, "can_#{name}?") do transitions.has_key? self.send(column).to_sym end @model.send(:define_method, "#{name}_transition") do transitions[self.send(column).to_sym] end end def transition(transitions) transitions.each_pair do |from, to| raise "Undefined state #{to}" unless @states.has_key? to Array(from).each do |f| raise "Undefined state #{f}" unless @states.has_key? f @transitions[f] = to end end end end end end