transitions 0.0.9 → 0.0.10

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/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- transitions (0.0.9)
4
+ transitions (0.0.10)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
data/README.rdoc CHANGED
@@ -1,41 +1,88 @@
1
- = transitions
1
+ = What is transitions?
2
2
 
3
- The gem is based on Rick Olson's code of ActiveModel::StateMachine,
4
- axed from ActiveModel in {this
5
- commit}[http://github.com/rails/rails/commit/db49c706b62e7ea2ab93f05399dbfddf5087ee0c].
3
+ transitions is a ruby state machine implementation based on Rick Olsons
4
+ ActiveModel::StateMachine. It was extracted from ActiveModel and turned
5
+ into a gem when it got the axe in commit {db49c706b}[http://github.com/rails/rails/commit/db49c706b62e7ea2ab93f05399dbfddf5087ee0c].
6
6
 
7
- == Installation
7
+ I really encourage you to try {state_machine}[https://github.com/pluginaweek/state_machine] before using this gem. Currently I have no time to maintain the gem, if you want to add some new features - contact with me.
8
8
 
9
- If you're using Rails + ActiveRecord + Bundler
9
+ == Quick Example
10
10
 
11
- # in your Gemfile
12
- gem "transitions", :require => ["transitions", "active_record/transitions"]
11
+ require 'transitions'
12
+
13
+ class Product
14
+ include Transitions
15
+
16
+ state_machine do
17
+ state :available # first one is initial state
18
+ state :out_of_stock, :exit => :exit_out_of_stock
19
+ state :discontinued, :enter => lambda { |product| product.cancel_orders }
20
+
21
+ event :discontinued do
22
+ transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
23
+ end
24
+ event :out_of_stock do
25
+ transitions :to => :out_of_stock, :from => [:available, :discontinued]
26
+ end
27
+ event :available do
28
+ transitions :to => :available, :from => [:out_of_stock], :guard => lambda { |product| product.in_stock > 0 }
29
+ end
30
+ end
31
+ end
13
32
 
14
- # in your AR models that will use the state machine
15
- include ::Transitions
16
- include ActiveRecord::Transitions
33
+ == Using on_transition
17
34
 
18
- state_machine do
19
- state :available # first one is initial state
20
- state :out_of_stock
21
- state :discontinue
35
+ Each event definition takes an optional "on_transition" argument, which allows you to execute methods on transition.
36
+ You can pass in a Symbol, a String, a Proc or an Array containing method names as Symbol or String like this:
22
37
 
23
- event :discontinue do
24
- transitions :to => :discontinue, :from => [:available, :out_of_stock], :on_transition => :do_discontinue
25
- end
26
- event :out_of_stock do
27
- transitions :to => :out_of_stock, :from => [:available, :discontinue]
28
- end
29
- event :available do
30
- transitions :to => :available, :from => [:out_of_stock], :on_transition => :send_alerts
31
- end
38
+ event :discontinue do
39
+ transitions :to => :discontinued, :from => [:available, :out_of_stock], :on_transition => [:do_discontinue, :notify_clerk]
32
40
  end
33
41
 
34
- == Usage
42
+ == Using with Rails
35
43
 
36
- Krzysiek Heród (aka {Netizer}[http://github.com/netizer]) wrote a nice {blog
37
- post}[http://dev.netizer.pl/transitions-state-machine-for-rails-3.html]
38
- about using Transitions in ActiveRecord.
44
+ This goes into your Gemfile:
45
+
46
+ gem "transitions", :require => ["transitions", "active_record/transitions"]
47
+
48
+ … and this into your AR model:
49
+
50
+ include ActiveRecord::Transitions
51
+
52
+ === A note about persistence
53
+ The property used to persist the models’ state is named <tt>state</tt> (really!),
54
+ which should be a string column wide enough to fit your longest state name.
55
+ It should also be mentioned that <tt>#save!</tt> is called after every successful event.
56
+
57
+ == Event execution flow
58
+
59
+ On an event, with our quick example product going from <tt>:available</tt> to
60
+ <tt>:discontinued</tt> it looks like this:
61
+
62
+ 1. <tt>baby_ninja.discontinue!(:reason => :pirates)</tt>
63
+ 2. call <tt>:exit</tt> handler of <tt>:available</tt> state
64
+ 3. call <tt>:guard</tt> of <tt>:available to :discontinue</tt> transition within <tt>#discontinue</tt> event
65
+ 4. call <tt>#event_failed(:event)</tt> and abort unless <tt>3.</tt> returned <tt>true</tt>
66
+ 5. call <tt>:on_transition(:reason => :pirates)</tt> of <tt>:available to :discontinue</tt> transition within <tt>#discontinue</tt> event
67
+ 6. call <tt>:enter</tt> handler of <tt>:discontinue</tt>
68
+ 7. call <tt>#event_fired(:available, :discontinue)</tt>
69
+ 8. call <tt>#write_state(machine, :discontinue)</tt>
70
+ 9. call <tt>#write_state_without_persistence(machine, :discontinue)</tt>
71
+ 10. call <tt>baby_ninja#:success</tt> handler method of <tt>#discontinue</tt> event
72
+
73
+ === A note about events
74
+
75
+ When you declare an event <tt>discontinue</tt>, two methods are declared for
76
+ you: <tt>discontinue</tt> and <tt>discontinue!</tt>. Both events will call
77
+ <tt>write_state_without_persistence</tt> on successful transition, but only the
78
+ bang(!)-version will call <tt>write_state</tt>.
79
+
80
+ == Documentation, Guides & Examples
81
+
82
+ - {Online API Documentation}[http://rdoc.info/github/qoobaa/transitions/master/Transitions]
83
+ - Krzysiek Heród (aka {Netizer}[http://github.com/netizer]) wrote a nice
84
+ {blog post}[http://dev.netizer.pl/transitions-state-machine-for-rails-3.html]
85
+ about using Transitions in ActiveRecord.
39
86
 
40
87
  == Copyright
41
88
 
@@ -26,24 +26,39 @@ module ActiveRecord
26
26
 
27
27
  included do
28
28
  include ::Transitions
29
- before_validation :set_initial_state
29
+ after_initialize :set_initial_state
30
30
  validates_presence_of :state
31
31
  validate :state_inclusion
32
32
  end
33
33
 
34
+ def reload
35
+ super.tap do
36
+ self.class.state_machines.values.each do |sm|
37
+ remove_instance_variable(sm.current_state_variable) if instance_variable_defined?(sm.current_state_variable)
38
+ end
39
+ end
40
+ end
41
+
34
42
  protected
35
43
 
36
44
  def write_state(state_machine, state)
45
+ ivar = state_machine.current_state_variable
46
+ prev_state = current_state(state_machine.name)
47
+ instance_variable_set(ivar, state)
37
48
  self.state = state.to_s
38
49
  save!
50
+ rescue ActiveRecord::RecordInvalid
51
+ self.state = prev_state.to_s
52
+ instance_variable_set(ivar, prev_state)
53
+ raise
39
54
  end
40
55
 
41
56
  def read_state(state_machine)
42
- self.state.to_sym
57
+ self.state && self.state.to_sym
43
58
  end
44
59
 
45
60
  def set_initial_state
46
- self.state ||= self.class.state_machine.initial_state.to_s
61
+ self.state ||= self.class.state_machine.initial_state.to_s if self.has_attribute?(:state)
47
62
  end
48
63
 
49
64
  def state_inclusion
data/lib/transitions.rb CHANGED
@@ -27,7 +27,7 @@ require "transitions/state_transition"
27
27
  require "transitions/version"
28
28
 
29
29
  module Transitions
30
- class InvalidTransition < Exception
30
+ class InvalidTransition < StandardError
31
31
 
32
32
  end
33
33
 
@@ -46,6 +46,15 @@ module Transitions
46
46
  obj.send(@on_transition, *args)
47
47
  when Proc
48
48
  @on_transition.call(obj, *args)
49
+ when Array
50
+ @on_transition.each do |callback|
51
+ # Yes, we're passing always the same parameters for each callback in here.
52
+ # We should probably drop args altogether in case we get an array.
53
+ obj.send(callback, *args)
54
+ end
55
+ else
56
+ # TODO We probably should check for this in the constructor and not that late.
57
+ 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?
49
58
  end
50
59
  end
51
60
 
@@ -1,3 +1,3 @@
1
1
  module Transitions
2
- VERSION = "0.0.9"
2
+ VERSION = "0.0.10"
3
3
  end
@@ -3,7 +3,10 @@ require 'active_support/core_ext/module/aliasing'
3
3
 
4
4
  class CreateTrafficLights < ActiveRecord::Migration
5
5
  def self.up
6
- create_table(:traffic_lights) { |t| t.string :state }
6
+ create_table(:traffic_lights) do |t|
7
+ t.string :state
8
+ t.string :name
9
+ end
7
10
  end
8
11
  end
9
12
 
@@ -43,6 +46,10 @@ class ValidatingTrafficLight < TrafficLight
43
46
  validate {|t| errors.add(:base, 'This TrafficLight will never validate after creation') unless t.new_record? }
44
47
  end
45
48
 
49
+ class ConditionalValidatingTrafficLight < TrafficLight
50
+ validates(:name, :presence => true, :if => :red?)
51
+ end
52
+
46
53
  class TestActiveRecord < Test::Unit::TestCase
47
54
  def setup
48
55
  ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
@@ -52,6 +59,11 @@ class TestActiveRecord < Test::Unit::TestCase
52
59
  @light = TrafficLight.create!
53
60
  end
54
61
 
62
+ test "new record has the initial state set" do
63
+ @light = TrafficLight.new
64
+ assert_equal "off", @light.state
65
+ end
66
+
55
67
  test "states initial state" do
56
68
  assert @light.off?
57
69
  assert_equal :off, @light.current_state
@@ -104,9 +116,37 @@ class TestActiveRecord < Test::Unit::TestCase
104
116
  end
105
117
 
106
118
  test "transition raises exception when model validation fails" do
107
- validating_light = ValidatingTrafficLight.create!
119
+ validating_light = ValidatingTrafficLight.create!(:name => 'Foobar')
108
120
  assert_raise(ActiveRecord::RecordInvalid) do
109
121
  validating_light.reset!
110
122
  end
111
123
  end
124
+
125
+ test "state query method used in a validation condition" do
126
+ validating_light = ConditionalValidatingTrafficLight.create!
127
+ assert_raise(ActiveRecord::RecordInvalid) do
128
+ validating_light.reset!
129
+ end
130
+ assert(validating_light.off?)
131
+ end
132
+
133
+ test "reloading model resets current state" do
134
+ @light.reset
135
+ assert @light.red?
136
+ @light.update_attribute(:state, 'green')
137
+ assert @light.reload.green?, "reloaded state should come from database, not instance variable"
138
+ end
139
+
140
+ end
141
+
142
+ class TestNewActiveRecord < TestActiveRecord
143
+
144
+ def setup
145
+ @light = TrafficLight.new
146
+ end
147
+
148
+ test "new active records defaults current state to the initial state" do
149
+ assert_equal :off, @light.current_state
150
+ end
151
+
112
152
  end
@@ -0,0 +1,43 @@
1
+ require 'helper'
2
+
3
+ class Car
4
+ include Transitions
5
+
6
+ state_machine do
7
+ state :parked
8
+ state :running
9
+ state :driving
10
+
11
+ event :turn_key do
12
+ transitions :from => :parked, :to => :running, :on_transition => :start_engine
13
+ end
14
+
15
+ event :start_driving do
16
+ transitions :from => :parked, :to => :driving, :on_transition => [:start_engine, :loosen_handbrake, :push_gas_pedal]
17
+ end
18
+ end
19
+
20
+ %w!start_engine loosen_handbrake push_gas_pedal!.each do |m|
21
+ define_method(m){}
22
+ end
23
+ end
24
+
25
+ class TestStateTransitionCallbacks < Test::Unit::TestCase
26
+ def setup
27
+ @car = Car.new
28
+ end
29
+
30
+ test "should execute callback defined via 'on_transition'" do
31
+ @car.expects(:start_engine)
32
+ @car.turn_key!
33
+ end
34
+
35
+ test "should execute multiple callbacks defined via 'on_transition' in the same order they were defined" do
36
+ on_transition_sequence = sequence('on_transition_sequence')
37
+
38
+ @car.expects(:start_engine).in_sequence(on_transition_sequence)
39
+ @car.expects(:loosen_handbrake).in_sequence(on_transition_sequence)
40
+ @car.expects(:push_gas_pedal).in_sequence(on_transition_sequence)
41
+ @car.start_driving!
42
+ end
43
+ end
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: transitions
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 0
8
- - 9
9
- version: 0.0.9
4
+ prerelease:
5
+ version: 0.0.10
10
6
  platform: ruby
11
7
  authors:
12
8
  - "Jakub Ku\xC5\xBAma"
@@ -14,7 +10,7 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2010-09-21 00:00:00 +02:00
13
+ date: 2011-08-11 00:00:00 +02:00
18
14
  default_executable:
19
15
  dependencies:
20
16
  - !ruby/object:Gem::Dependency
@@ -24,8 +20,6 @@ dependencies:
24
20
  requirements:
25
21
  - - ~>
26
22
  - !ruby/object:Gem::Version
27
- segments:
28
- - 1
29
23
  version: "1"
30
24
  type: :development
31
25
  prerelease: false
@@ -37,8 +31,6 @@ dependencies:
37
31
  requirements:
38
32
  - - ~>
39
33
  - !ruby/object:Gem::Version
40
- segments:
41
- - 2
42
34
  version: "2"
43
35
  type: :development
44
36
  prerelease: false
@@ -50,8 +42,6 @@ dependencies:
50
42
  requirements:
51
43
  - - ">="
52
44
  - !ruby/object:Gem::Version
53
- segments:
54
- - 0
55
45
  version: "0"
56
46
  type: :development
57
47
  prerelease: false
@@ -63,8 +53,6 @@ dependencies:
63
53
  requirements:
64
54
  - - ">="
65
55
  - !ruby/object:Gem::Version
66
- segments:
67
- - 0
68
56
  version: "0"
69
57
  type: :development
70
58
  prerelease: false
@@ -76,8 +64,6 @@ dependencies:
76
64
  requirements:
77
65
  - - ~>
78
66
  - !ruby/object:Gem::Version
79
- segments:
80
- - 3
81
67
  version: "3"
82
68
  type: :development
83
69
  prerelease: false
@@ -112,6 +98,7 @@ files:
112
98
  - test/test_machine.rb
113
99
  - test/test_state.rb
114
100
  - test/test_state_transition.rb
101
+ - test/test_state_transition_callbacks.rb
115
102
  - test/test_state_transition_guard_check.rb
116
103
  - transitions.gemspec
117
104
  has_rdoc: true
@@ -128,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
128
115
  requirements:
129
116
  - - ">="
130
117
  - !ruby/object:Gem::Version
131
- hash: -568409294339076591
118
+ hash: 347067155
132
119
  segments:
133
120
  - 0
134
121
  version: "0"
@@ -137,15 +124,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
124
  requirements:
138
125
  - - ">="
139
126
  - !ruby/object:Gem::Version
140
- segments:
141
- - 1
142
- - 3
143
- - 6
144
127
  version: 1.3.6
145
128
  requirements: []
146
129
 
147
130
  rubyforge_project: transitions
148
- rubygems_version: 1.3.7
131
+ rubygems_version: 1.6.2
149
132
  signing_key:
150
133
  specification_version: 3
151
134
  summary: State machine extracted from ActiveModel