edge-state-machine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|