edge-state-machine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.rspec +4 -0
- data/.travis.yml +1 -0
- data/Gemfile +4 -0
- data/README.rdoc +63 -0
- data/Rakefile +20 -0
- data/edge-state-machine.gemspec +31 -0
- data/lib/active_record/edge-state-machine.rb +34 -0
- data/lib/edge-state-machine/event.rb +107 -0
- data/lib/edge-state-machine/machine.rb +85 -0
- data/lib/edge-state-machine/state.rb +45 -0
- data/lib/edge-state-machine/state_transition.rb +47 -0
- data/lib/edge-state-machine/version.rb +3 -0
- data/lib/edge-state-machine.rb +71 -0
- data/lib/mongoid/edge-state-machine.rb +34 -0
- data/spec/active_record_helper.rb +10 -0
- data/spec/active_record_spec.rb +248 -0
- data/spec/event_spec.rb +76 -0
- data/spec/machine_spec.rb +44 -0
- data/spec/migrations/create_orders.rb +12 -0
- data/spec/migrations/create_traffic_lights.rb +8 -0
- data/spec/mongoid_helper.rb +12 -0
- data/spec/mongoid_spec.rb +251 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/state_spec.rb +191 -0
- metadata +167 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm: 1.9.2
|
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= Travis Build Status
|
2
|
+
|
3
|
+
{<img src="https://secure.travis-ci.org/danpersa/edge-state-machine.png"/>}[http://travis-ci.org/danpersa/edge-state-machine]
|
4
|
+
|
5
|
+
= Edge State Machine
|
6
|
+
|
7
|
+
The gem is based on Rick Olson's code of ActiveModel::StateMachine,
|
8
|
+
axed from ActiveModel in {this
|
9
|
+
commit}[http://github.com/rails/rails/commit/db49c706b62e7ea2ab93f05399dbfddf5087ee0c].
|
10
|
+
|
11
|
+
And on Krzysiek Heród's gem, {Transitions}[https://github.com/netizer/transitions], which added Mongoid support.
|
12
|
+
|
13
|
+
== Installation
|
14
|
+
|
15
|
+
If you're using Rails + ActiveRecord + Bundler
|
16
|
+
|
17
|
+
# in your Gemfile
|
18
|
+
gem "edge-state-machine", :require => ["edge-state-machine", "active_record/edge-state-machine"]
|
19
|
+
|
20
|
+
# in your AR models that will use the state machine
|
21
|
+
include ::EdgeStateMachine
|
22
|
+
include ActiveRecord::EdgeStateMachine
|
23
|
+
|
24
|
+
state_machine do
|
25
|
+
state :available # first one is initial state
|
26
|
+
state :out_of_stock
|
27
|
+
state :discontinue
|
28
|
+
|
29
|
+
event :discontinue do
|
30
|
+
transitions :to => :discontinue, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
|
31
|
+
end
|
32
|
+
event :out_of_stock do
|
33
|
+
transitions :to => :out_of_stock, :from => [:available, :discontinue]
|
34
|
+
end
|
35
|
+
event :available do
|
36
|
+
transitions :to => :available, :from => [:out_of_stock], :on_transition => :send_alerts
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
If you're using Rails + Mongoid + Bundler
|
41
|
+
|
42
|
+
# in your Gemfile
|
43
|
+
gem "edge-state-machine", :require => ["edge-state-machine", "mongoid/edge-state-machine"]
|
44
|
+
|
45
|
+
# in your AR models that will use the state machine
|
46
|
+
include ::EdgeStateMachine
|
47
|
+
include Mongoid::EdgeStateMachine
|
48
|
+
|
49
|
+
state_machine do
|
50
|
+
state :available # first one is initial state
|
51
|
+
state :out_of_stock
|
52
|
+
state :discontinue
|
53
|
+
|
54
|
+
event :discontinue do
|
55
|
+
transitions :to => :discontinue, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
|
56
|
+
end
|
57
|
+
event :out_of_stock do
|
58
|
+
transitions :to => :out_of_stock, :from => [:available, :discontinue]
|
59
|
+
end
|
60
|
+
event :available do
|
61
|
+
transitions :to => :available, :from => [:out_of_stock], :on_transition => :send_alerts
|
62
|
+
end
|
63
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
|
6
|
+
Bundler::GemHelper.install_tasks
|
7
|
+
Bundler.setup
|
8
|
+
|
9
|
+
require 'rake'
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'rspec/core/rake_task'
|
13
|
+
desc 'Run RSpecs to confirm that all functionality is working as expected'
|
14
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
15
|
+
t.pattern = 'spec/**/*_spec.rb'
|
16
|
+
end
|
17
|
+
task :default => :spec
|
18
|
+
rescue LoadError
|
19
|
+
puts "Hiding spec tasks because RSpec is not available"
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "edge-state-machine/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "edge-state-machine"
|
7
|
+
s.version = EdgeStateMachine::VERSION
|
8
|
+
s.authors = ["Dan Persa"]
|
9
|
+
s.email = ["dan.persa@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/danpersa/edge-state-machine"
|
11
|
+
s.summary = %q{State machine extracted from ActiveModel}
|
12
|
+
s.description = %q{Lightweight state machine extracted from ActiveModel}
|
13
|
+
|
14
|
+
s.rubyforge_project = "edge-state-machine"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
|
22
|
+
# specify any dependencies here; for example:
|
23
|
+
s.add_development_dependency 'rspec', '~> 2.6'
|
24
|
+
s.add_development_dependency 'rake'
|
25
|
+
s.add_development_dependency 'mongoid'
|
26
|
+
s.add_development_dependency 'bson_ext'
|
27
|
+
s.add_development_dependency 'sqlite3-ruby'
|
28
|
+
s.add_development_dependency 'activerecord'
|
29
|
+
|
30
|
+
# s.add_runtime_dependency "rest-client"
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module EdgeStateMachine
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
include ::EdgeStateMachine
|
7
|
+
after_initialize :set_initial_state
|
8
|
+
validates_presence_of :state
|
9
|
+
validate :state_inclusion
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def write_state(state_machine, state)
|
15
|
+
self.state = state.to_s
|
16
|
+
save!
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_state(state_machine)
|
20
|
+
self.state.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_initial_state
|
24
|
+
self.state ||= self.class.state_machine.initial_state.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def state_inclusion
|
28
|
+
unless self.class.state_machine.states.map{|s| s.name.to_s }.include?(self.state.to_s)
|
29
|
+
self.errors.add(:state, :inclusion, :value => self.state)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module EdgeStateMachine
|
2
|
+
class Event
|
3
|
+
attr_reader :name, :success, :timestamp
|
4
|
+
|
5
|
+
def initialize(machine, name, options = {}, &block)
|
6
|
+
@machine, @name, @transitions = machine, name, []
|
7
|
+
if machine
|
8
|
+
machine.klass.send(:define_method, "#{name}!") do |*args|
|
9
|
+
machine.fire_event(name, self, true, *args)
|
10
|
+
end
|
11
|
+
|
12
|
+
machine.klass.send(:define_method, name.to_s) do |*args|
|
13
|
+
machine.fire_event(name, self, false, *args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
update(options, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def fire(obj, to_state = nil, *args)
|
20
|
+
transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
|
21
|
+
raise InvalidTransition if transitions.size == 0
|
22
|
+
|
23
|
+
next_state = nil
|
24
|
+
transitions.each do |transition|
|
25
|
+
next if to_state && !Array(transition.to).include?(to_state)
|
26
|
+
if transition.perform(obj)
|
27
|
+
next_state = to_state || Array(transition.to).first
|
28
|
+
transition.execute(obj, *args)
|
29
|
+
break
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# Update timestamps on obj if a timestamp has been defined
|
33
|
+
update_event_timestamp(obj, next_state) if timestamp_defined?
|
34
|
+
next_state
|
35
|
+
end
|
36
|
+
|
37
|
+
def transitions_from_state?(state)
|
38
|
+
@transitions.any? { |t| t.from? state }
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(event)
|
42
|
+
if event.is_a? Symbol
|
43
|
+
name == event
|
44
|
+
else
|
45
|
+
name == event.name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Has the timestamp option been specified for this event?
|
50
|
+
def timestamp_defined?
|
51
|
+
!@timestamp.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def update(options = {}, &block)
|
55
|
+
@success = options[:success] if options.key?(:success)
|
56
|
+
self.timestamp = options[:timestamp] if options[:timestamp]
|
57
|
+
instance_eval(&block) if block
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# update the timestamp attribute on obj
|
62
|
+
def update_event_timestamp(obj, next_state)
|
63
|
+
obj.send "#{timestamp_attribute_name(obj, next_state)}=", Time.now
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set the timestamp attribute.
|
67
|
+
# @raise [ArgumentError] timestamp should be either a String, Symbol or true
|
68
|
+
def timestamp=(value)
|
69
|
+
case value
|
70
|
+
when String, Symbol, TrueClass
|
71
|
+
@timestamp = value
|
72
|
+
else
|
73
|
+
raise ArgumentError, "timestamp must be either: true, a String or a Symbol"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# Returns the name of the timestamp attribute for this event
|
81
|
+
# If the timestamp was simply true it returns the default_timestamp_name
|
82
|
+
# otherwise, returns the user-specified timestamp name
|
83
|
+
def timestamp_attribute_name(obj, next_state)
|
84
|
+
timestamp == true ? default_timestamp_name(obj, next_state) : @timestamp
|
85
|
+
end
|
86
|
+
|
87
|
+
# If @timestamp is true, try a default timestamp name
|
88
|
+
def default_timestamp_name(obj, next_state)
|
89
|
+
at_name = "%s_at" % next_state
|
90
|
+
on_name = "%s_on" % next_state
|
91
|
+
case
|
92
|
+
when obj.respond_to?(at_name) then at_name
|
93
|
+
when obj.respond_to?(on_name) then on_name
|
94
|
+
else
|
95
|
+
raise NoMethodError, "Couldn't find a suitable timestamp field for event: #{@name}.
|
96
|
+
Please define #{at_name} or #{on_name} in #{obj.class}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def transitions(trans_opts)
|
102
|
+
Array(trans_opts[:from]).each do |s|
|
103
|
+
@transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module EdgeStateMachine
|
2
|
+
class Machine
|
3
|
+
attr_writer :initial_state
|
4
|
+
attr_accessor :states, :events, :state_index
|
5
|
+
attr_reader :klass, :name, :auto_scopes
|
6
|
+
|
7
|
+
def initialize(klass, name, options = {}, &block)
|
8
|
+
@klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
|
9
|
+
update(options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initial_state
|
13
|
+
@initial_state ||= (states.first ? states.first.name : nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
def update(options = {}, &block)
|
17
|
+
@initial_state = options[:initial] if options.key?(:initial)
|
18
|
+
@auto_scopes = options[:auto_scopes]
|
19
|
+
instance_eval(&block) if block
|
20
|
+
include_scopes if @auto_scopes && defined?(ActiveRecord::Base) && @klass < ActiveRecord::Base
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def fire_event(event, record, persist, *args)
|
25
|
+
state_index[record.current_state(@name)].call_action(:exit, record)
|
26
|
+
if new_state = @events[event].fire(record, nil, *args)
|
27
|
+
state_index[new_state].call_action(:enter, record)
|
28
|
+
|
29
|
+
if record.respond_to?(event_fired_callback)
|
30
|
+
record.send(event_fired_callback, record.current_state, new_state, event)
|
31
|
+
end
|
32
|
+
|
33
|
+
record.current_state(@name, new_state, persist)
|
34
|
+
record.send(@events[event].success) if @events[event].success
|
35
|
+
true
|
36
|
+
else
|
37
|
+
if record.respond_to?(event_failed_callback)
|
38
|
+
record.send(event_failed_callback, event)
|
39
|
+
end
|
40
|
+
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def states_for_select
|
46
|
+
states.map { |st| [st.display_name, st.name.to_s] }
|
47
|
+
end
|
48
|
+
|
49
|
+
def events_for(state)
|
50
|
+
events = @events.values.select { |event| event.transitions_from_state?(state) }
|
51
|
+
events.map! { |event| event.name }
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_state_variable
|
55
|
+
"@#{@name}_current_state"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def state(name, options = {})
|
61
|
+
@states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def event(name, options = {}, &block)
|
65
|
+
(@events[name] ||= Event.new(self, name)).update(options, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def event_fired_callback
|
69
|
+
@event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
|
70
|
+
end
|
71
|
+
|
72
|
+
def event_failed_callback
|
73
|
+
@event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
|
74
|
+
end
|
75
|
+
|
76
|
+
def include_scopes
|
77
|
+
@states.each do |state|
|
78
|
+
state_name = state.name.to_s
|
79
|
+
raise InvalidMethodOverride if @klass.respond_to?(state_name)
|
80
|
+
@klass.scope state_name, @klass.where(:state => state_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module EdgeStateMachine
|
2
|
+
class State
|
3
|
+
attr_reader :name, :options
|
4
|
+
|
5
|
+
def initialize(name, options = {})
|
6
|
+
@name = name
|
7
|
+
if machine = options.delete(:machine)
|
8
|
+
machine.klass.define_state_query_method(name)
|
9
|
+
end
|
10
|
+
update(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(state)
|
14
|
+
if state.is_a? Symbol
|
15
|
+
name == state
|
16
|
+
else
|
17
|
+
name == state.name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def call_action(action, record)
|
22
|
+
action = @options[action]
|
23
|
+
case action
|
24
|
+
when Symbol, String
|
25
|
+
record.send(action)
|
26
|
+
when Proc
|
27
|
+
action.call(record)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def display_name
|
32
|
+
@display_name ||= name.to_s.gsub(/_/, ' ').capitalize
|
33
|
+
end
|
34
|
+
|
35
|
+
def for_select
|
36
|
+
[display_name, name.to_s]
|
37
|
+
end
|
38
|
+
|
39
|
+
def update(options = {})
|
40
|
+
@display_name = options.delete(:display) if options.key?(:display)
|
41
|
+
@options = options
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module EdgeStateMachine
|
2
|
+
class StateTransition
|
3
|
+
attr_reader :from, :to, :options
|
4
|
+
|
5
|
+
def initialize(opts)
|
6
|
+
@from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
|
7
|
+
@options = opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform(obj)
|
11
|
+
case @guard
|
12
|
+
when Symbol, String
|
13
|
+
obj.send(@guard)
|
14
|
+
when Proc
|
15
|
+
@guard.call(obj)
|
16
|
+
else
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(obj, *args)
|
22
|
+
case @on_transition
|
23
|
+
when Symbol, String
|
24
|
+
obj.send(@on_transition, *args)
|
25
|
+
when Proc
|
26
|
+
@on_transition.call(obj, *args)
|
27
|
+
when Array
|
28
|
+
@on_transition.each do |callback|
|
29
|
+
# Yes, we're passing always the same parameters for each callback in here.
|
30
|
+
# We should probably drop args altogether in case we get an array.
|
31
|
+
obj.send(callback, *args)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
# TODO We probably should check for this in the constructor and not that late.
|
35
|
+
raise ArgumentError, "You can only pass a Symbol, a String, a Proc or an Array to 'on_transition' - got #{@on_transition.class}." unless @on_transition.nil?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(obj)
|
40
|
+
@from == obj.from && @to == obj.to
|
41
|
+
end
|
42
|
+
|
43
|
+
def from?(value)
|
44
|
+
@from == value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "edge-state-machine/event"
|
2
|
+
require "edge-state-machine/machine"
|
3
|
+
require "edge-state-machine/state"
|
4
|
+
require "edge-state-machine/state_transition"
|
5
|
+
require "edge-state-machine/version"
|
6
|
+
|
7
|
+
module EdgeStateMachine
|
8
|
+
class InvalidTransition < StandardError; end
|
9
|
+
class InvalidMethodOverride < StandardError; end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def inherited(klass)
|
13
|
+
super
|
14
|
+
klass.state_machines = state_machines
|
15
|
+
end
|
16
|
+
|
17
|
+
def state_machines
|
18
|
+
@state_machines ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def state_machines=(value)
|
22
|
+
@state_machines = value ? value.dup : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def state_machine(name = nil, options = {}, &block)
|
26
|
+
if name.is_a?(Hash)
|
27
|
+
options = name
|
28
|
+
name = nil
|
29
|
+
end
|
30
|
+
name ||= :default
|
31
|
+
state_machines[name] ||= Machine.new(self, name)
|
32
|
+
block ? state_machines[name].update(options, &block) : state_machines[name]
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_state_query_method(state_name)
|
36
|
+
name = "#{state_name}?"
|
37
|
+
undef_method(name) if method_defined?(name)
|
38
|
+
class_eval "def #{name}; current_state.to_s == %(#{state_name}) end"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.included(base)
|
43
|
+
base.extend(ClassMethods)
|
44
|
+
end
|
45
|
+
|
46
|
+
def current_state(name = nil, new_state = nil, persist = false)
|
47
|
+
sm = self.class.state_machine(name)
|
48
|
+
ivar = sm.current_state_variable
|
49
|
+
if name && new_state
|
50
|
+
if persist && respond_to?(:write_state)
|
51
|
+
write_state(sm, new_state)
|
52
|
+
end
|
53
|
+
|
54
|
+
if respond_to?(:write_state_without_persistence)
|
55
|
+
write_state_without_persistence(sm, new_state)
|
56
|
+
end
|
57
|
+
|
58
|
+
instance_variable_set(ivar, new_state)
|
59
|
+
else
|
60
|
+
instance_variable_set(ivar, nil) unless instance_variable_defined?(ivar)
|
61
|
+
value = instance_variable_get(ivar)
|
62
|
+
return value if value
|
63
|
+
|
64
|
+
if respond_to?(:read_state)
|
65
|
+
value = instance_variable_set(ivar, read_state(sm))
|
66
|
+
end
|
67
|
+
|
68
|
+
value || sm.initial_state
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module EdgeStateMachine
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
include ::EdgeStateMachine
|
7
|
+
after_initialize :set_initial_state
|
8
|
+
validates_presence_of :state
|
9
|
+
validate :state_inclusion
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def write_state(state_machine, state)
|
15
|
+
self.state = state.to_s
|
16
|
+
save!
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_state(state_machine)
|
20
|
+
self.state.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_initial_state
|
24
|
+
self.state ||= self.class.state_machine.initial_state.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def state_inclusion
|
28
|
+
unless self.class.state_machine.states.map{|s| s.name.to_s }.include?(self.state.to_s)
|
29
|
+
self.errors.add(:state, :inclusion, :value => self.state)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
require 'active_support/core_ext/module/aliasing'
|
4
|
+
require 'migrations/create_orders'
|
5
|
+
require 'migrations/create_traffic_lights'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", 'lib'))
|
8
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
9
|
+
|
10
|
+
require 'active_record/edge-state-machine'
|