module DataMapper
module Is
module StateMachine
# Event DSL (Domain Specific Language)
module EventDsl
# Define an event. This takes a block which describes all valid
# transitions for this event.
#
# Example:
#
# class TrafficLight
# include DataMapper::Resource
# property :id, Serial
# is :state_machine, :initial => :green, :column => :color do
# # state definitions go here...
#
# event :forward do
# transition :from => :green, :to => :yellow
# transition :from => :yellow, :to => :red
# transition :from => :red, :to => :green
# end
# end
# end
#
# +transition+ takes a hash where :to is the state to transition
# to and :from is a state (or Array of states) from which this
# event can be fired.
def event(name, &block)
unless state_machine_context?(:is)
raise InvalidContext, "Valid only in 'is :state_machine' block"
end
if method_defined?("#{name}!")
raise InvalidEvent, "There is a method called #{name}! on #{self}"
end
event_object = create_event(name)
# ===== Setup context =====
@is_state_machine[:event] = {
:name => name,
:object => event_object
}
push_state_machine_context(:event)
# ===== Define methods =====
define_method("#{name}!") do
transition!(name)
end
# Possible alternative to the above:
# (class_eval is typically faster than define_method)
#
# self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
# def #{name}!
# machine.current_state_name = __send__(:"#{column}")
# machine.fire_event(name, self)
# __send__(:"#{column}="), machine.current_state_name
# end
# RUBY
yield if block_given?
# ===== Teardown context =====
pop_state_machine_context
end
def destroy(options)
unless state_machine_context?(:is)
raise InvalidContext, "Valid only in 'is :state_machine' block"
end
event_object = create_event(:destroy)
from = options[:from]
to = options[:to]
via = options[:via]
event_object.add_transition(from, to, via)
end
def create_event(name)
unless state_machine_context?(:is)
raise InvalidContext, "Valid only in 'is :state_machine' block"
end
name = name.to_s
definition = @is_state_machine[:definition]
event = Data::Event.new(name, definition)
definition.events << event
event
end
def transition(options)
unless state_machine_context?(:event)
raise InvalidContext, "Valid only in 'event' block"
end
event_name = @is_state_machine[:event][:name]
event_object = @is_state_machine[:event][:object]
from = options[:from]
to = options[:to]
via = options[:via]
event_object.add_transition(from, to, via)
end
end # EventDsl
end # StateMachine
end # Is
end # DataMapper