fdv / typo

Typo is the oldest and most powerful Ruby on Rails blogware, providing custom templates, powerful drag and drop plugins API, advanced SEO capabilities, XMLRPC API and many more.

This URL has Read+Write access

typo / lib / stateful.rb
100644 112 lines (93 sloc) 2.953 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
module Stateful
  class State
    def initialize(model)
      @model = model
    end
 
    def to_s
      self.class.to_s.demodulize
    end
 
    def exit_hook(target_state)
      RAILS_DEFAULT_LOGGER.debug("#{model} leaving state #{self}")
    end
 
    def enter_hook
      RAILS_DEFAULT_LOGGER.debug("#{model} entering state #{self}")
    end
 
 
    def method_missing(predicate, *args)
      if predicate.to_s.last == '?'
        self.class.to_s.demodulize.underscore == predicate.to_s.chop
      else
        if block_given?
          super(predicate, *args) { |*block_args| yield(*block_args) }
        else
          super(predicate, *args)
        end
      end
    end
 
    def ==(other_state)
      self.class == other_state.class
    end
 
    def hash
      self.class.hash
    end
 
    private
    attr_reader :model
  end
 
  def self.included(base)
    base.extend ClassMethods
  end
 
  module ClassMethods
    def has_state(field, options = {})
      options.assert_valid_keys(:valid_states, :handles, :initial_state)
 
      unless states = options[:valid_states]
        raise "You must specify at least one state"
      end
      states = states.collect &:to_sym
 
      delegations = Set.new(options[:handles]) + states.collect { |value| "#{value}?" }
 
      initial_state = options[:initial_state] || states.first
 
      state_writer_method(field, states, initial_state)
      state_reader_method(field, states, initial_state)
 
      delegations.each do |value|
        delegate value, :to => field
      end
    end
 
    def state_reader_method(name, states, initial_state)
      module_eval <<-end_meth
def #{name}(force_reload = false)
if @#{name}_obj.nil? || force_reload
memento = read_attribute(#{name.inspect}) || #{initial_state.inspect}
unless #{states.inspect}.include? memento.to_sym
raise \"Invalid state: \#{memento} in the database.\"
end
@#{name}_obj = self.class.class_eval(memento.to_s.classify).new(self)
end
@#{name}_obj
end
end_meth
    end
 
    def state_writer_method(name, states, initial_state)
      module_eval <<-end_meth
def #{name}=(state)
case state
when Symbol
set_#{name}_from_symbol state
when String
set_#{name}_from_symbol state.to_sym
else
raise "You must set the state with a symbol or a string"
end
end
 
def set_#{name}_from_symbol(memento)
unless #{states.inspect}.include?(memento)
raise "Invalid state: " + memento
end
self[:#{name}] = memento.to_s
new_state = self.class.class_eval(memento.to_s.classify).new(self)
@#{name}_obj.exit_hook(new_state) if @#{name}_obj
@#{name}_obj = new_state
@#{name}_obj.enter_hook
@#{name}_obj
end
end_meth
    end
  end
end