simple_state_machine 0.3.2 → 0.3.3
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 +28 -14
- data/VERSION +1 -1
- data/lib/simple_state_machine/active_record.rb +10 -7
- data/lib/simple_state_machine/simple_state_machine.rb +5 -1
- data/simple_state_machine.gemspec +2 -2
- data/spec/active_record_spec.rb +21 -2
- data/spec/decorator_spec.rb +4 -4
- data/spec/simple_state_machine_spec.rb +6 -2
- metadata +4 -4
data/README.rdoc
CHANGED
@@ -2,27 +2,42 @@
|
|
2
2
|
|
3
3
|
A simple DSL to decorate existing methods with state transition guards.
|
4
4
|
|
5
|
-
|
5
|
+
Instead of using a DSL to define events, SimpleStateMachine decorates methods
|
6
|
+
to help you encapsulate state and guard state transitions.
|
6
7
|
|
7
|
-
|
8
|
-
state transitions.
|
8
|
+
== Example usage
|
9
9
|
|
10
|
-
|
10
|
+
Write a method, arguments are allowed:
|
11
11
|
|
12
|
+
def activate_account(activation_code)
|
13
|
+
# call other methods, no need to add these in callbacks
|
14
|
+
...
|
15
|
+
log.debug "Try to activate account with #{activation_code}"
|
16
|
+
end
|
17
|
+
|
18
|
+
Now mark the method as an event and specify how the state should transition
|
19
|
+
when the method is called. In this example, the activate_account method will
|
20
|
+
set the state to :active if the initial state is :pending.
|
21
|
+
|
22
|
+
event :activate_account, :pending => :active
|
23
|
+
|
24
|
+
|
25
|
+
That's it!
|
26
|
+
You can now call the method and the state will automatically change.
|
27
|
+
If the state change is not allowed, a SimpleStateMachine::Error is raised.
|
28
|
+
|
29
|
+
=== Example usage with ActiveRecord / ActiveModel
|
30
|
+
When using ActiveRecord / ActiveModel you can add an error to the errors object.
|
31
|
+
This will prevent the state from being changed.
|
32
|
+
|
12
33
|
def activate_account(activation_code)
|
13
34
|
if activation_code_invalid?(activation_code)
|
14
35
|
errors.add(:activation_code, 'Invalid')
|
15
36
|
end
|
16
37
|
end
|
17
|
-
event :activate_account, :pending => :active
|
18
38
|
|
19
39
|
|
20
|
-
|
21
|
-
- No need for defining callbacks or other methods to add logic
|
22
|
-
- Arguments can be passed to events
|
23
|
-
- Validation errors can be set on the model if you are using ActiveRecord / ActiveModel
|
24
|
-
|
25
|
-
== Example
|
40
|
+
== More complete implementation
|
26
41
|
|
27
42
|
To add a state machine:
|
28
43
|
- extend SimpleStateMachine
|
@@ -43,9 +58,8 @@ To add a state machine:
|
|
43
58
|
event :push_switch_1, :off => :on,
|
44
59
|
:on => :off
|
45
60
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
61
|
+
# define another event
|
62
|
+
# note that implementation of :push_switch_2 is optional
|
49
63
|
event :push_switch_2, :off => :on,
|
50
64
|
:on => :off
|
51
65
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.3
|
@@ -10,10 +10,12 @@ module SimpleStateMachine::ActiveRecord
|
|
10
10
|
|
11
11
|
def decorate transition
|
12
12
|
super transition
|
13
|
-
|
14
|
-
|
13
|
+
event_name = transition.event_name.to_s
|
14
|
+
event_name_and_save = "#{event_name}_and_save"
|
15
|
+
unless @subject.method_defined?(event_name_and_save)
|
16
|
+
@subject.send(:define_method, event_name_and_save) do |*args|
|
15
17
|
old_state = self.send(self.class.state_machine_definition.state_method)
|
16
|
-
send
|
18
|
+
send event_name, *args
|
17
19
|
if !self.errors.entries.empty?
|
18
20
|
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
19
21
|
return false
|
@@ -27,10 +29,11 @@ module SimpleStateMachine::ActiveRecord
|
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
30
|
-
|
31
|
-
|
32
|
+
event_name_and_save_bang = "#{event_name_and_save}!"
|
33
|
+
unless @subject.method_defined?(event_name_and_save_bang)
|
34
|
+
@subject.send(:define_method, event_name_and_save_bang) do |*args|
|
32
35
|
old_state = self.send(self.class.state_machine_definition.state_method)
|
33
|
-
send
|
36
|
+
send event_name, *args
|
34
37
|
if !self.errors.entries.empty?
|
35
38
|
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
36
39
|
raise ActiveRecord::RecordInvalid.new(self)
|
@@ -42,7 +45,7 @@ module SimpleStateMachine::ActiveRecord
|
|
42
45
|
raise #re raise
|
43
46
|
end
|
44
47
|
end
|
45
|
-
@subject.send :alias_method, "#{transition.event_name}!",
|
48
|
+
@subject.send :alias_method, "#{transition.event_name}!", event_name_and_save_bang
|
46
49
|
end
|
47
50
|
end
|
48
51
|
|
@@ -1,4 +1,8 @@
|
|
1
1
|
module SimpleStateMachine
|
2
|
+
|
3
|
+
class Error < ::RuntimeError
|
4
|
+
end
|
5
|
+
|
2
6
|
##
|
3
7
|
# Adds state machine methods to extended class
|
4
8
|
module StateMachineMixin
|
@@ -103,7 +107,7 @@ module SimpleStateMachine
|
|
103
107
|
|
104
108
|
def illegal_event_callback event_name
|
105
109
|
# override with your own implementation, like setting errors in your model
|
106
|
-
raise "You cannot '#{event_name}' when state is '#{@subject.state}'"
|
110
|
+
raise Error.new("You cannot '#{event_name}' when state is '#{@subject.state}'")
|
107
111
|
end
|
108
112
|
|
109
113
|
end
|
@@ -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.3.
|
8
|
+
s.version = "0.3.3"
|
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-09-03}
|
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 = [
|
data/spec/active_record_spec.rb
CHANGED
@@ -5,6 +5,7 @@ gem 'activerecord', '~> 2.3.5'
|
|
5
5
|
require 'active_record'
|
6
6
|
require 'examples/user'
|
7
7
|
|
8
|
+
ActiveRecord::Base.logger = Logger.new STDOUT
|
8
9
|
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
9
10
|
|
10
11
|
def setup_db
|
@@ -68,6 +69,15 @@ describe ActiveRecord do
|
|
68
69
|
User.find(user.id).activation_code.should_not be_nil
|
69
70
|
end
|
70
71
|
|
72
|
+
it "persist transitions even when state is attr_protected" do
|
73
|
+
user_class = Class.new(User)
|
74
|
+
user_class.instance_eval { attr_protected :state }
|
75
|
+
user = user_class.create!(:name => 'name', :state => 'x')
|
76
|
+
user.should be_new
|
77
|
+
user.invite_and_save
|
78
|
+
user.reload.should be_invited
|
79
|
+
end
|
80
|
+
|
71
81
|
it "persists transitions when using send and a symbol" do
|
72
82
|
user = User.create!(:name => 'name')
|
73
83
|
user.send(:invite_and_save).should == true
|
@@ -78,7 +88,7 @@ describe ActiveRecord do
|
|
78
88
|
it "raises an error if an invalid state_transition is called" do
|
79
89
|
user = User.create!(:name => 'name')
|
80
90
|
l = lambda { user.confirm_invitation_and_save 'abc' }
|
81
|
-
l.should raise_error(
|
91
|
+
l.should raise_error(SimpleStateMachine::Error, "You cannot 'confirm_invitation' when state is 'new'")
|
82
92
|
end
|
83
93
|
|
84
94
|
it "returns false and keeps state if record is invalid" do
|
@@ -109,10 +119,19 @@ describe ActiveRecord do
|
|
109
119
|
User.find(user.id).activation_code.should_not be_nil
|
110
120
|
end
|
111
121
|
|
122
|
+
it "persist transitions even when state is attr_protected" do
|
123
|
+
user_class = Class.new(User)
|
124
|
+
user_class.instance_eval { attr_protected :state }
|
125
|
+
user = user_class.create!(:name => 'name', :state => 'x')
|
126
|
+
user.should be_new
|
127
|
+
user.invite_and_save!
|
128
|
+
user.reload.should be_invited
|
129
|
+
end
|
130
|
+
|
112
131
|
it "raises an error if an invalid state_transition is called" do
|
113
132
|
user = User.create!(:name => 'name')
|
114
133
|
l = lambda { user.confirm_invitation_and_save! 'abc' }
|
115
|
-
l.should raise_error(
|
134
|
+
l.should raise_error(SimpleStateMachine::Error, "You cannot 'confirm_invitation' when state is 'new'")
|
116
135
|
end
|
117
136
|
|
118
137
|
it "raises a RecordInvalid and keeps state if record is invalid" do
|
data/spec/decorator_spec.rb
CHANGED
@@ -27,12 +27,12 @@ end
|
|
27
27
|
describe SimpleStateMachine::Decorator do
|
28
28
|
|
29
29
|
it "defines state_helper_methods for all states" do
|
30
|
-
TrafficLight.new.green?.should
|
30
|
+
TrafficLight.new.green?.should == true
|
31
31
|
TrafficLight.new.orange?.should == false
|
32
|
-
TrafficLight.new.red?.should
|
32
|
+
TrafficLight.new.red?.should == false
|
33
33
|
end
|
34
34
|
|
35
|
-
it "does not define
|
35
|
+
it "does not define a state_helper_method if it already exists" do
|
36
36
|
l = lambda { WithPredefinedStateHelperMethods.new.unread? }
|
37
37
|
l.should raise_error(RuntimeError, 'blah')
|
38
38
|
end
|
@@ -41,4 +41,4 @@ describe SimpleStateMachine::Decorator do
|
|
41
41
|
WithoutEventMethods.new.view
|
42
42
|
end
|
43
43
|
|
44
|
-
end
|
44
|
+
end
|
@@ -15,7 +15,11 @@ class SimpleExample
|
|
15
15
|
end
|
16
16
|
|
17
17
|
describe SimpleStateMachine do
|
18
|
-
|
18
|
+
|
19
|
+
it "has an error that extends RuntimeError" do
|
20
|
+
SimpleStateMachine::Error.superclass.should == RuntimeError
|
21
|
+
end
|
22
|
+
|
19
23
|
it "has a default state" do
|
20
24
|
SimpleExample.new.state.should == 'state1'
|
21
25
|
end
|
@@ -40,7 +44,7 @@ describe SimpleStateMachine do
|
|
40
44
|
|
41
45
|
it "raise an error if an invalid state_transition is called" do
|
42
46
|
example = SimpleExample.new
|
43
|
-
lambda { example.event2 }.should raise_error(
|
47
|
+
lambda { example.event2 }.should raise_error(SimpleStateMachine::Error, "You cannot 'event2' when state is 'state1'")
|
44
48
|
end
|
45
49
|
|
46
50
|
it "return nil" do
|
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: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 0.3.
|
9
|
+
- 3
|
10
|
+
version: 0.3.3
|
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-09-03 00:00:00 +02:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|