simple_state_machine 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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
- SimpleStateMachine helps you to encapsulate state and guard state transitions.
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
- Instead of using a DSL to define events, SimpleStateMachine decorates existing methods with logic that guards
8
- state transitions.
8
+ == Example usage
9
9
 
10
- ==== example
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
- Decorating methods has a couple of advantages:
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
- def push_switch_2
47
- puts 'pushed switch 2 #{state}'
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.2
1
+ 0.3.3
@@ -10,10 +10,12 @@ module SimpleStateMachine::ActiveRecord
10
10
 
11
11
  def decorate transition
12
12
  super transition
13
- unless @subject.method_defined?("#{transition.event_name}_and_save")
14
- @subject.send(:define_method, "#{transition.event_name}_and_save") do |*args|
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 "#{transition.event_name}", *args
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
- unless @subject.method_defined?("#{transition.event_name}_and_save!")
31
- @subject.send(:define_method, "#{transition.event_name}_and_save!") do |*args|
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 "#{transition.event_name}", *args
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}!", "#{transition.event_name}_and_save!"
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.2"
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-08-30}
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 = [
@@ -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(RuntimeError, "You cannot 'confirm_invitation' when state is 'new'")
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(RuntimeError, "You cannot 'confirm_invitation' when state is 'new'")
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
@@ -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 == true
30
+ TrafficLight.new.green?.should == true
31
31
  TrafficLight.new.orange?.should == false
32
- TrafficLight.new.red?.should == false
32
+ TrafficLight.new.red?.should == false
33
33
  end
34
34
 
35
- it "does not define an state_helper_method if it already exists" do
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(RuntimeError, "You cannot 'event2' when state is 'state1'")
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: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 2
10
- version: 0.3.2
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-08-30 00:00:00 +02:00
19
+ date: 2010-09-03 00:00:00 +02:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency