simple_state_machine 0.0.0 → 0.1.0
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/README.rdoc +10 -10
- data/VERSION +1 -1
- data/examples/user.rb +4 -4
- data/lib/simple_state_machine/active_record.rb +51 -0
- data/lib/simple_state_machine/simple_state_machine.rb +100 -160
- data/lib/simple_state_machine.rb +2 -1
- data/simple_state_machine.gemspec +3 -2
- data/spec/active_record_spec.rb +27 -26
- metadata +5 -4
data/README.rdoc
CHANGED
@@ -22,10 +22,10 @@ state transitions.
|
|
22
22
|
|
23
23
|
|
24
24
|
This has a couple of advantages:
|
25
|
+
- Encapsulate state transitions (no need for :guards),
|
25
26
|
- Arguments can be passed to 'events',
|
26
27
|
- 'events' can return a value, remember, it's just a method,
|
27
28
|
- Validation errors can be set on the model if you are using ActiveRecord / ActiveModel,
|
28
|
-
- Encapsulate state transitions (no need for :guards).
|
29
29
|
|
30
30
|
To use the code, you need to do 3 things:
|
31
31
|
- extend SimpleStateMachine,
|
@@ -34,7 +34,7 @@ To use the code, you need to do 3 things:
|
|
34
34
|
|
35
35
|
==== Example usage
|
36
36
|
|
37
|
-
class
|
37
|
+
class LampWithHotelSwitch
|
38
38
|
|
39
39
|
extend SimpleStateMachine
|
40
40
|
|
@@ -42,17 +42,17 @@ To use the code, you need to do 3 things:
|
|
42
42
|
self.state = :off
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
46
|
-
puts
|
45
|
+
def push_switch_1
|
46
|
+
puts 'pushed switch 1 #{state}'
|
47
47
|
end
|
48
|
-
event :
|
49
|
-
|
48
|
+
event :push_switch_1, :off => :on,
|
49
|
+
:on => :off
|
50
50
|
|
51
|
-
def
|
52
|
-
puts
|
51
|
+
def push_switch_2
|
52
|
+
puts 'pushed switch 2 #{state}'
|
53
53
|
end
|
54
|
-
event :
|
55
|
-
|
54
|
+
event :push_switch_2, :off => :on,
|
55
|
+
:on => :off
|
56
56
|
|
57
57
|
end
|
58
58
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.1.0
|
data/examples/user.rb
CHANGED
@@ -3,7 +3,7 @@ class User < ActiveRecord::Base
|
|
3
3
|
|
4
4
|
validates_presence_of :name
|
5
5
|
|
6
|
-
extend SimpleStateMachine
|
6
|
+
extend SimpleStateMachine::ActiveRecord
|
7
7
|
|
8
8
|
def after_initialize
|
9
9
|
self.state ||= 'new'
|
@@ -16,8 +16,8 @@ class User < ActiveRecord::Base
|
|
16
16
|
event :invite, :new => :invited
|
17
17
|
|
18
18
|
def confirm_invitation activation_code
|
19
|
-
if activation_code !=
|
20
|
-
errors.add
|
19
|
+
if self.activation_code != activation_code
|
20
|
+
errors.add 'activation_code', 'is invalid'
|
21
21
|
end
|
22
22
|
end
|
23
23
|
event :confirm_invitation, :invited => :active
|
@@ -34,4 +34,4 @@ class User < ActiveRecord::Base
|
|
34
34
|
true
|
35
35
|
end
|
36
36
|
|
37
|
-
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SimpleStateMachine::ActiveRecord
|
2
|
+
|
3
|
+
include SimpleStateMachine::EventMixin
|
4
|
+
|
5
|
+
def state_machine_decorator subject
|
6
|
+
Decorator.new subject
|
7
|
+
end
|
8
|
+
|
9
|
+
class Decorator < SimpleStateMachine::Decorator
|
10
|
+
|
11
|
+
def decorate event_name, from, to
|
12
|
+
super event_name, from, to
|
13
|
+
unless @subject.method_defined?("#{event_name}_and_save")
|
14
|
+
@subject.send(:define_method, "#{event_name}_and_save") do |*args|
|
15
|
+
old_state = state
|
16
|
+
send "#{event_name}", *args
|
17
|
+
if save
|
18
|
+
return true
|
19
|
+
else
|
20
|
+
self.state = old_state
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
unless @subject.method_defined?("#{event_name}_and_save!")
|
26
|
+
@subject.send(:define_method, "#{event_name}_and_save!") do |*args|
|
27
|
+
old_state = state
|
28
|
+
send "#{event_name}", *args
|
29
|
+
if !self.errors.entries.empty?
|
30
|
+
self.state = old_state
|
31
|
+
raise ActiveRecord::RecordInvalid.new(self)
|
32
|
+
end
|
33
|
+
begin
|
34
|
+
save!
|
35
|
+
rescue ActiveRecord::RecordInvalid
|
36
|
+
self.state = old_state
|
37
|
+
raise
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def define_state_setter_method; end
|
46
|
+
|
47
|
+
def define_state_getter_method; end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -1,209 +1,149 @@
|
|
1
1
|
module SimpleStateMachine
|
2
2
|
|
3
|
-
|
4
|
-
@state_machine_definition ||= StateMachineDefinition.new self
|
5
|
-
@state_machine_definition.define_event event_name, state_transitions
|
6
|
-
end
|
7
|
-
|
8
|
-
def state_machine_definition
|
9
|
-
@state_machine_definition
|
10
|
-
end
|
11
|
-
|
12
|
-
class StateMachineDefinition
|
13
|
-
|
14
|
-
attr_reader :events
|
3
|
+
module EventMixin
|
15
4
|
|
16
|
-
def
|
17
|
-
@events = {}
|
18
|
-
@decorator = if inherits_from_active_record_base?(subject)
|
19
|
-
Decorator::ActiveRecord.new(subject)
|
20
|
-
else
|
21
|
-
Decorator::Base.new(subject)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def define_event event_name, state_transitions
|
26
|
-
@events[event_name.to_s] ||= {}
|
5
|
+
def event event_name, state_transitions
|
27
6
|
state_transitions.each do |from, to|
|
28
|
-
|
29
|
-
|
7
|
+
state_machine_definition.add_transition(event_name, from, to)
|
8
|
+
state_machine_decorator(self).decorate( event_name, from, to)
|
30
9
|
end
|
31
10
|
end
|
32
11
|
|
33
|
-
def
|
34
|
-
|
12
|
+
def state_machine_definition
|
13
|
+
@state_machine_definition ||= StateMachineDefinition.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def state_machine_decorator subject
|
17
|
+
Decorator.new subject
|
35
18
|
end
|
36
19
|
|
37
20
|
end
|
21
|
+
|
22
|
+
include EventMixin
|
38
23
|
|
39
|
-
|
40
|
-
|
41
|
-
class Base
|
42
|
-
def initialize(subject)
|
43
|
-
@subject = subject
|
44
|
-
end
|
45
|
-
|
46
|
-
def next_state(event_name)
|
47
|
-
@subject.class.state_machine_definition.events[event_name.to_s][@subject.state]
|
48
|
-
end
|
49
|
-
|
50
|
-
def transition(event_name)
|
51
|
-
if to = next_state(event_name)
|
52
|
-
result = yield
|
53
|
-
@subject.state = to
|
54
|
-
return result
|
55
|
-
else
|
56
|
-
illegal_event_callback event_name
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
24
|
+
class StateMachineDefinition
|
61
25
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
26
|
+
def transitions
|
27
|
+
@transitions ||= {}
|
28
|
+
end
|
66
29
|
|
30
|
+
def add_transition event_name, from, to
|
31
|
+
transitions[event_name.to_s] ||= {}
|
32
|
+
transitions[event_name.to_s][from.to_s] = to.to_s
|
67
33
|
end
|
68
34
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
if event_name =~ /\!$/
|
73
|
-
event_name = event_name.chop
|
74
|
-
end
|
75
|
-
super event_name
|
76
|
-
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class StateMachine
|
77
38
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
39
|
+
def initialize(subject)
|
40
|
+
@subject = subject
|
41
|
+
end
|
42
|
+
|
43
|
+
def next_state(event_name)
|
44
|
+
transitions[event_name.to_s][@subject.state]
|
45
|
+
end
|
46
|
+
|
47
|
+
def transition(event_name)
|
48
|
+
if to = next_state(event_name)
|
49
|
+
result = yield
|
50
|
+
# TODO refactor out to AR module
|
51
|
+
if defined?(::ActiveRecord) && @subject.is_a?(::ActiveRecord::Base)
|
52
|
+
if @subject.errors.entries.empty?
|
87
53
|
@subject.state = to
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
@subject.save
|
92
|
-
end
|
54
|
+
return true
|
55
|
+
else
|
56
|
+
return false
|
93
57
|
end
|
94
58
|
else
|
95
|
-
|
59
|
+
@subject.state = to
|
60
|
+
return result
|
96
61
|
end
|
62
|
+
else
|
63
|
+
illegal_event_callback event_name
|
97
64
|
end
|
98
|
-
|
99
|
-
private
|
100
|
-
|
101
|
-
def with_error_counting
|
102
|
-
original_errors_size = @subject.errors.size
|
103
|
-
yield
|
104
|
-
@subject.errors.size - original_errors_size
|
105
|
-
end
|
106
|
-
|
107
65
|
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def transitions
|
70
|
+
@subject.class.state_machine_definition.transitions
|
71
|
+
end
|
108
72
|
|
73
|
+
def illegal_event_callback event_name
|
74
|
+
# override with your own implementation, like setting errors in your model
|
75
|
+
raise "You cannot '#{event_name}' when state is '#{@subject.state}'"
|
76
|
+
end
|
77
|
+
|
109
78
|
end
|
110
79
|
|
111
|
-
|
112
|
-
class Base
|
80
|
+
class Decorator
|
113
81
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
def decorate event_name, from, to
|
122
|
-
define_state_helper_method(from)
|
123
|
-
define_state_helper_method(to)
|
124
|
-
define_event_method(event_name)
|
125
|
-
decorate_event_method(event_name)
|
126
|
-
end
|
82
|
+
def initialize(subject)
|
83
|
+
@subject = subject
|
84
|
+
define_state_machine_method
|
85
|
+
define_state_getter_method
|
86
|
+
define_state_setter_method
|
87
|
+
end
|
127
88
|
|
128
|
-
|
89
|
+
def decorate event_name, from, to
|
90
|
+
define_state_helper_method(from)
|
91
|
+
define_state_helper_method(to)
|
92
|
+
define_event_method(event_name)
|
93
|
+
decorate_event_method(event_name)
|
94
|
+
end
|
129
95
|
|
130
|
-
|
131
|
-
@subject.send(:define_method, "state_machine") do
|
132
|
-
@state_machine ||= StateMachine::Base.new(self)
|
133
|
-
end
|
134
|
-
end
|
96
|
+
private
|
135
97
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
self.state == state.to_s
|
140
|
-
end
|
141
|
-
end
|
98
|
+
def define_state_machine_method
|
99
|
+
@subject.send(:define_method, "state_machine") do
|
100
|
+
@state_machine ||= StateMachine.new(self)
|
142
101
|
end
|
102
|
+
end
|
143
103
|
|
144
|
-
|
145
|
-
|
146
|
-
|
104
|
+
def define_state_helper_method state
|
105
|
+
unless @subject.method_defined?("#{state.to_s}?")
|
106
|
+
@subject.send(:define_method, "#{state.to_s}?") do
|
107
|
+
self.state == state.to_s
|
147
108
|
end
|
148
109
|
end
|
110
|
+
end
|
149
111
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
@subject.send(:define_method, "with_managed_state_#{event_name}") do |*args|
|
154
|
-
return state_machine.transition(event_name) do
|
155
|
-
send("without_managed_state_#{event_name}", *args)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
@subject.send :alias_method, "without_managed_state_#{event_name}", event_name
|
159
|
-
@subject.send :alias_method, event_name, "with_managed_state_#{event_name}"
|
160
|
-
end
|
112
|
+
def define_event_method event_name
|
113
|
+
unless @subject.method_defined?("#{event_name}")
|
114
|
+
@subject.send(:define_method, "#{event_name}") {}
|
161
115
|
end
|
116
|
+
end
|
162
117
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
118
|
+
def decorate_event_method event_name
|
119
|
+
# TODO put in transaction for activeRecord?
|
120
|
+
unless @subject.method_defined?("with_managed_state_#{event_name}")
|
121
|
+
@subject.send(:define_method, "with_managed_state_#{event_name}") do |*args|
|
122
|
+
return state_machine.transition(event_name) do
|
123
|
+
send("without_managed_state_#{event_name}", *args)
|
167
124
|
end
|
168
125
|
end
|
126
|
+
@subject.send :alias_method, "without_managed_state_#{event_name}", event_name
|
127
|
+
@subject.send :alias_method, event_name, "with_managed_state_#{event_name}"
|
169
128
|
end
|
129
|
+
end
|
170
130
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
end
|
131
|
+
def define_state_setter_method
|
132
|
+
unless @subject.method_defined?('state=')
|
133
|
+
@subject.send(:define_method, 'state=') do |new_state|
|
134
|
+
@state = new_state.to_s
|
176
135
|
end
|
177
136
|
end
|
137
|
+
end
|
178
138
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
def decorate event_name, from, to
|
184
|
-
super event_name, from, to
|
185
|
-
unless @subject.method_defined?("#{event_name}!")
|
186
|
-
@subject.send(:define_method, "#{event_name}!") do |*args|
|
187
|
-
send "#{event_name}", *args
|
139
|
+
def define_state_getter_method
|
140
|
+
unless @subject.method_defined?('state')
|
141
|
+
@subject.send(:define_method, 'state') do
|
142
|
+
@state
|
188
143
|
end
|
189
144
|
end
|
190
|
-
decorate_event_method("#{event_name}!")
|
191
|
-
end
|
192
|
-
|
193
|
-
private
|
194
|
-
|
195
|
-
def define_state_machine_method
|
196
|
-
@subject.send(:define_method, "state_machine") do
|
197
|
-
@state_machine ||= StateMachine::ActiveRecord.new(self)
|
198
|
-
end
|
199
145
|
end
|
200
146
|
|
201
|
-
def define_state_setter_method; end
|
202
|
-
|
203
|
-
def define_state_getter_method; end
|
204
|
-
|
205
|
-
end
|
206
|
-
|
207
147
|
end
|
208
148
|
|
209
|
-
end
|
149
|
+
end
|
data/lib/simple_state_machine.rb
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
require 'simple_state_machine/simple_state_machine'
|
1
|
+
require 'simple_state_machine/simple_state_machine'
|
2
|
+
require 'simple_state_machine/active_record'
|
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{simple_state_machine}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Marek de Heus", "Petrik de Heus"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-08-12}
|
13
13
|
s.description = %q{A simple DSL to decorate existing methods with logic that guards state transitions.}
|
14
14
|
s.email = ["FIX@example.com"]
|
15
15
|
s.extra_rdoc_files = [
|
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
|
|
27
27
|
"examples/traffic_light.rb",
|
28
28
|
"examples/user.rb",
|
29
29
|
"lib/simple_state_machine.rb",
|
30
|
+
"lib/simple_state_machine/active_record.rb",
|
30
31
|
"lib/simple_state_machine/simple_state_machine.rb",
|
31
32
|
"simple_state_machine.gemspec",
|
32
33
|
"spec/active_record_spec.rb",
|
data/spec/active_record_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
gem 'activerecord', '
|
4
|
+
gem 'activerecord', '~> 2.3.5'
|
5
5
|
require 'active_record'
|
6
6
|
require 'examples/user'
|
7
7
|
|
@@ -40,44 +40,46 @@ describe User do
|
|
40
40
|
User.new.should be_new
|
41
41
|
end
|
42
42
|
|
43
|
+
# TODO needs nesting/grouping, seems to have some duplication
|
44
|
+
|
43
45
|
describe "events" do
|
44
46
|
it "persists transitions" do
|
45
47
|
user = User.create!(:name => 'name')
|
46
|
-
user.
|
48
|
+
user.invite_and_save.should == true
|
47
49
|
User.find(user.id).should be_invited
|
48
50
|
User.find(user.id).activation_code.should_not be_nil
|
49
51
|
end
|
50
52
|
|
51
53
|
it "persists transitions with !" do
|
52
54
|
user = User.create!(:name => 'name')
|
53
|
-
user.
|
55
|
+
user.invite_and_save!.should == true
|
54
56
|
User.find(user.id).should be_invited
|
55
57
|
User.find(user.id).activation_code.should_not be_nil
|
56
58
|
end
|
57
59
|
|
58
60
|
it "raises an error if an invalid state_transition is called" do
|
59
61
|
user = User.create!(:name => 'name')
|
60
|
-
l = lambda { user.
|
62
|
+
l = lambda { user.confirm_invitation_and_save 'abc' }
|
61
63
|
l.should raise_error(RuntimeError, "You cannot 'confirm_invitation' when state is 'new'")
|
62
64
|
end
|
63
65
|
|
64
66
|
it "returns false if event called and record is invalid" do
|
65
67
|
user = User.new
|
66
68
|
user.should_not be_valid
|
67
|
-
user.
|
69
|
+
user.invite_and_save.should == false
|
68
70
|
end
|
69
71
|
|
70
72
|
it "keeps state if event called and record is invalid" do
|
71
73
|
user = User.new
|
72
74
|
user.should_not be_valid
|
73
|
-
user.
|
75
|
+
user.invite_and_save.should == false
|
74
76
|
user.should be_new
|
75
77
|
end
|
76
78
|
|
77
79
|
it "raises a RecordInvalid event if called with ! and record is invalid" do
|
78
80
|
user = User.new
|
79
81
|
user.should_not be_valid
|
80
|
-
l = lambda { user.
|
82
|
+
l = lambda { user.invite_and_save! }
|
81
83
|
l.should raise_error(ActiveRecord::RecordInvalid, "Validation failed: Name can't be blank")
|
82
84
|
end
|
83
85
|
|
@@ -85,44 +87,43 @@ describe User do
|
|
85
87
|
user = User.new
|
86
88
|
user.should_not be_valid
|
87
89
|
begin
|
88
|
-
user.
|
90
|
+
user.invite_and_save!
|
89
91
|
rescue ActiveRecord::RecordInvalid;end
|
90
92
|
user.should be_new
|
91
93
|
end
|
92
94
|
|
93
|
-
it "
|
95
|
+
it "will inspect errors after event and reset state" do
|
94
96
|
user = User.create!(:name => 'name')
|
95
|
-
user.
|
96
|
-
user.should
|
97
|
-
|
97
|
+
user.invite_and_save!
|
98
|
+
user.should be_invited
|
99
|
+
user.confirm_invitation('x')
|
100
|
+
user.errors.entries.should == [['activation_code', 'is invalid']]
|
101
|
+
user.should be_invited
|
98
102
|
end
|
99
103
|
|
100
|
-
it "
|
104
|
+
it "returns false if event adds errors" do
|
101
105
|
user = User.create!(:name => 'name')
|
102
|
-
user.
|
103
|
-
user.should
|
104
|
-
user.confirm_invitation('x')
|
105
|
-
user.should be_invited
|
106
|
+
user.invite_and_save!
|
107
|
+
user.confirm_invitation('x').should == false
|
106
108
|
end
|
107
109
|
|
108
|
-
it "raises a RecordInvalid if
|
110
|
+
it "raises a RecordInvalid if 'event'_and_save! is called and event adds errors" do
|
109
111
|
user = User.create!(:name => 'name')
|
110
|
-
user.
|
111
|
-
user.
|
112
|
-
l
|
113
|
-
l.should raise_error(ActiveRecord::RecordInvalid, "Validation failed: Activation code Invalid")
|
112
|
+
user.invite_and_save!
|
113
|
+
l = lambda { user.confirm_invitation_and_save!('x') }
|
114
|
+
l.should raise_error(ActiveRecord::RecordInvalid, "Validation failed: Activation code is invalid")
|
114
115
|
end
|
115
116
|
|
116
117
|
it "keeps state if record is valid but event adds errors" do
|
117
118
|
user = User.create!(:name => 'name')
|
118
|
-
user.
|
119
|
-
user.should
|
119
|
+
user.invite_and_save!
|
120
|
+
user.should be_invited
|
120
121
|
begin
|
121
|
-
user.
|
122
|
+
user.confirm_invitation_and_save!('x')
|
122
123
|
rescue ActiveRecord::RecordInvalid;end
|
123
124
|
user.should be_invited
|
124
125
|
end
|
125
126
|
|
126
127
|
end
|
127
128
|
|
128
|
-
end
|
129
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_state_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.0
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Marek de Heus
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-
|
19
|
+
date: 2010-08-12 00:00:00 +02:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -56,6 +56,7 @@ files:
|
|
56
56
|
- examples/traffic_light.rb
|
57
57
|
- examples/user.rb
|
58
58
|
- lib/simple_state_machine.rb
|
59
|
+
- lib/simple_state_machine/active_record.rb
|
59
60
|
- lib/simple_state_machine/simple_state_machine.rb
|
60
61
|
- simple_state_machine.gemspec
|
61
62
|
- spec/active_record_spec.rb
|