class CanHasState::Definition

Attributes

column[R]
initial_state[R]
states[R]
triggers[R]

Public Class Methods

new(column_name, parent_context, &block) click to toggle source
# File lib/can_has_state/definition.rb, line 6
def initialize(column_name, parent_context, &block)
  @parent_context = parent_context
  @column = column_name.to_sym
  @states = {}
  @triggers = []
  instance_eval(&block)
  @initial_state ||= @states.keys.first
end

Public Instance Methods

allow?(record, to) click to toggle source
# File lib/can_has_state/definition.rb, line 84
def allow?(record, to)
  to &&= to.to_s
  return false unless known?(to)
  states[to][:guards].all? do |g|
    case g
    when Proc
      g.call record
    when Symbol, String
      record.send g
    else
      raise ArgumentError, "Expecing Symbol or Proc for :guard, got #{g.class} : #{g}"
    end
  end
end
extend_machine(&block) click to toggle source
# File lib/can_has_state/definition.rb, line 16
def extend_machine(&block)
  instance_eval(&block)
end
known?(to) click to toggle source
# File lib/can_has_state/definition.rb, line 79
def known?(to)
  to &&= to.to_s
  @states.keys.include? to
end
message(to) click to toggle source
# File lib/can_has_state/definition.rb, line 99
def message(to)
  to &&= to.to_s
  states[to][:message]
end
on(pairs) click to toggle source
# File lib/can_has_state/definition.rb, line 67
def on(pairs)
  trigger  = pairs.delete :trigger
  deferred = pairs.delete :deferred
  raise(ArgumentError, "use of deferred triggers requires support for #after_save callbacks") if deferred && !@parent_context.respond_to?(:after_save)
  pairs.each do |from, to|
    @triggers << {:from=>Array(from).map(&:to_s), :to=>Array(to).map(&:to_s), 
                  :trigger=>Array(trigger), :type=>:trigger, :deferred=>!!deferred}
  end
end
state(state_name, *args) click to toggle source
# File lib/can_has_state/definition.rb, line 21
def state(state_name, *args)
  options = args.extract_options!
  state_name = state_name.to_s

  if args.include? :initial
    @initial_state = state_name
  end

  # TODO: turn even guards into types of triggers ... then support :guard as a trigger param
  guards = []
  message = :invalid_transition
  # TODO: differentiate messages for :from errors vs. :guard errors

  options.each do |key, val|
    case key
    when :from
      from_vals = Array(val).map(&:to_s)
      from_vals << nil # for new records
      guards << Proc.new do |r|
        val_was = r.send("#{column}_was")
        val_was &&= val_was.to_s
        from_vals.include? val_was
      end
    when :guard, :require
      guards += Array(val)
    when :message
      message = val
    when :timestamp
      @triggers << {:from=>["*"], :to=>[state_name], :trigger=>[Proc.new{|r| r.send("#{val}=", Time.now.utc)}]}
    when :on_enter
      @triggers << {:from=>["*"], :to=>[state_name], :trigger=>Array(val), :type=>:on_enter}
    when :on_enter_deferred
      raise(ArgumentError, "use of deferred triggers requires support for #after_save callbacks") unless @parent_context.respond_to?(:after_save)
      @triggers << {:from=>["*"], :to=>[state_name], :trigger=>Array(val), :type=>:on_enter, :deferred=>true}
    when :on_exit
      @triggers << {:from=>[state_name], :to=>["*"], :trigger=>Array(val), :type=>:on_exit}
    when :on_exit_deferred
      raise(ArgumentError, "use of deferred triggers requires support for #after_save callbacks") unless @parent_context.respond_to?(:after_save)
      @triggers << {:from=>[state_name], :to=>["*"], :trigger=>Array(val), :type=>:on_exit, :deferred=>true}
    end
  end

  @states[state_name] = {:guards=>guards, :message=>message}
end
trigger(record, from, to, deferred=false) click to toggle source
# File lib/can_has_state/definition.rb, line 105
def trigger(record, from, to, deferred=false)
  from &&= from.to_s
  to &&= to.to_s
  # Rails.logger.debug "Checking triggers for transition #{from} to #{to} (deferred:#{deferred.inspect})"
  @triggers.select do |trigger|
    deferred ? trigger[:deferred] : !trigger[:deferred]
  end.select do |trigger|
    (trigger[:from].include?("*") || trigger[:from].include?(from)) &&
        (trigger[:to].include?("*") || trigger[:to].include?(to))
  # end.each do |trigger|
  #   Rails.logger.debug "  Matched trigger: #{trigger[:from].inspect} -- #{trigger[:to].inspect}"
  end.each do |trigger|
    call_triggers record, trigger
  end
end

Private Instance Methods

call_triggers(record, trigger) click to toggle source
# File lib/can_has_state/definition.rb, line 124
def call_triggers(record, trigger)
  trigger[:trigger].each do |m|
    case m
    when Proc
      m.call record
    when Symbol, String
      record.send m
    else
      raise ArgumentError, "Expecing Symbol or Proc for #{trigger[:type].inspect}, got #{m.class} : #{m}"
    end
  end
end