145 lines
4.5 KiB
Ruby
145 lines
4.5 KiB
Ruby
module AASM
|
|
class InstanceBase
|
|
attr_accessor :from_state, :to_state, :current_event
|
|
|
|
def initialize(instance, name=:default) # instance of the class including AASM, name of the state machine
|
|
@instance = instance
|
|
@name = name
|
|
end
|
|
|
|
def current_state
|
|
@instance.aasm_read_state(@name)
|
|
end
|
|
|
|
def current_state=(state)
|
|
@instance.aasm_write_state_without_persistence(state, @name)
|
|
end
|
|
|
|
def enter_initial_state
|
|
state_name = determine_state_name(@instance.class.aasm(@name).initial_state)
|
|
state_object = state_object_for_name(state_name)
|
|
|
|
state_object.fire_callbacks(:before_enter, @instance)
|
|
self.current_state = state_name
|
|
state_object.fire_callbacks(:after_enter, @instance)
|
|
|
|
state_name
|
|
end
|
|
|
|
def human_state
|
|
state_object_for_name(current_state).display_name
|
|
end
|
|
|
|
def states(options={}, *args)
|
|
if options.has_key?(:permitted)
|
|
selected_events = events({:permitted => options[:permitted]}, *args)
|
|
# An array of arrays. Each inner array represents the transitions that
|
|
# transition from the current state for an event
|
|
event_transitions = selected_events.map {|e| e.transitions_from_state(current_state) }
|
|
|
|
# An array of :to transition states
|
|
to_state_names = event_transitions.map do |transitions|
|
|
return nil if transitions.empty?
|
|
|
|
# Return the :to state of the first transition that is allowed (or not) or nil
|
|
if options[:permitted]
|
|
transition = transitions.find { |t| t.allowed?(@instance, *args) }
|
|
else
|
|
transition = transitions.find { |t| !t.allowed?(@instance, *args) }
|
|
end
|
|
transition ? transition.to : nil
|
|
end.flatten.compact.uniq
|
|
|
|
# Select states that are in to_state_names
|
|
@instance.class.aasm(@name).states.select {|s| to_state_names.include?(s.name)}
|
|
else
|
|
@instance.class.aasm(@name).states
|
|
end
|
|
end
|
|
|
|
def events(options={}, *args)
|
|
state = options[:state] || current_state
|
|
events = @instance.class.aasm(@name).events.select {|e| e.transitions_from_state?(state) }
|
|
|
|
options[:reject] = Array(options[:reject])
|
|
events.reject! { |e| options[:reject].include?(e.name) }
|
|
|
|
if options.has_key?(:permitted)
|
|
# filters the results of events_for_current_state so that only those that
|
|
# are really currently possible (given transition guards) are shown.
|
|
if options[:permitted]
|
|
events.select! { |e| @instance.send("may_#{e.name}?", *args) }
|
|
else
|
|
events.select! { |e| !@instance.send("may_#{e.name}?", *args) }
|
|
end
|
|
end
|
|
|
|
events
|
|
end
|
|
|
|
def permitted_transitions
|
|
events(permitted: true).flat_map do |event|
|
|
available_transitions = event.transitions_from_state(current_state)
|
|
allowed_transitions = available_transitions.select { |t| t.allowed?(@instance) }
|
|
|
|
allowed_transitions.map do |transition|
|
|
{ event: event.name, state: transition.to }
|
|
end
|
|
end
|
|
end
|
|
|
|
def state_object_for_name(name)
|
|
obj = @instance.class.aasm(@name).states.find {|s| s.name == name}
|
|
raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
|
|
obj
|
|
end
|
|
|
|
def determine_state_name(state)
|
|
case state
|
|
when Symbol, String
|
|
state
|
|
when Proc
|
|
state.call(@instance)
|
|
else
|
|
raise NotImplementedError, "Unrecognized state-type given. Expected Symbol, String, or Proc."
|
|
end
|
|
end
|
|
|
|
def may_fire_event?(name, *args)
|
|
if event = @instance.class.aasm(@name).state_machine.events[name]
|
|
!!event.may_fire?(@instance, *args)
|
|
else
|
|
false # unknown event
|
|
end
|
|
end
|
|
|
|
def fire(event_name, *args, &block)
|
|
event_exists?(event_name)
|
|
|
|
@instance.send(event_name, *args, &block)
|
|
end
|
|
|
|
def fire!(event_name, *args, &block)
|
|
event_exists?(event_name, true)
|
|
bang_event_name = "#{event_name}!".to_sym
|
|
@instance.send(bang_event_name, *args, &block)
|
|
end
|
|
|
|
def set_current_state_with_persistence(state)
|
|
save_success = @instance.aasm_write_state(state, @name)
|
|
self.current_state = state if save_success
|
|
save_success
|
|
end
|
|
|
|
private
|
|
|
|
def event_exists?(event_name, bang = false)
|
|
event = @instance.class.aasm(@name).state_machine.events[event_name.to_sym]
|
|
return true if event
|
|
|
|
event_error = bang ? "#{event_name}!" : event_name
|
|
raise AASM::UndefinedEvent, "Event :#{event_error} doesn't exist" if event.nil?
|
|
end
|
|
end
|
|
end
|