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 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