state_machine 0.8.0 → 0.8.1
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/CHANGELOG.rdoc +19 -0
- data/README.rdoc +24 -4
- data/Rakefile +10 -16
- data/lib/state_machine.rb +1 -1
- data/lib/state_machine/event_collection.rb +4 -6
- data/lib/state_machine/integrations/active_record.rb +100 -23
- data/lib/state_machine/integrations/active_record/observer.rb +5 -1
- data/lib/state_machine/integrations/data_mapper.rb +22 -4
- data/lib/state_machine/integrations/sequel.rb +6 -7
- data/lib/state_machine/machine.rb +29 -10
- data/lib/state_machine/machine_collection.rb +20 -13
- data/lib/state_machine/state.rb +4 -4
- data/{tasks → lib/tasks}/state_machine.rake +0 -0
- data/{tasks → lib/tasks}/state_machine.rb +1 -1
- data/test/functional/state_machine_test.rb +21 -1
- data/test/unit/event_collection_test.rb +9 -0
- data/test/unit/event_test.rb +45 -1
- data/test/unit/guard_test.rb +1 -1
- data/test/unit/integrations/active_record_test.rb +651 -325
- data/test/unit/integrations/data_mapper_test.rb +954 -404
- data/test/unit/integrations/sequel_test.rb +628 -189
- data/test/unit/machine_collection_test.rb +223 -18
- data/test/unit/machine_test.rb +16 -13
- data/test/unit/state_test.rb +14 -15
- metadata +70 -78
@@ -5,10 +5,14 @@ module StateMachine
|
|
5
5
|
# values are only set if the machine's attribute doesn't already exist
|
6
6
|
# (which must mean the defaults are being skipped)
|
7
7
|
def initialize_states(object, options = {})
|
8
|
+
if ignore = options[:ignore]
|
9
|
+
ignore.map! {|attribute| attribute.to_sym}
|
10
|
+
end
|
11
|
+
|
8
12
|
each_value do |machine|
|
9
|
-
if !options.include?(:dynamic) || machine.dynamic_initial_state? == options[:dynamic]
|
13
|
+
if (!ignore || !ignore.include?(machine.attribute)) && (!options.include?(:dynamic) || machine.dynamic_initial_state? == options[:dynamic])
|
10
14
|
value = machine.read(object, :state)
|
11
|
-
machine.write(object, :state, machine.initial_state(object).value) if value.nil? || value.respond_to?(:empty?) && value.empty?
|
15
|
+
machine.write(object, :state, machine.initial_state(object).value) if ignore || value.nil? || value.respond_to?(:empty?) && value.empty?
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -117,28 +121,31 @@ module StateMachine
|
|
117
121
|
|
118
122
|
# Make sure all events were valid
|
119
123
|
if result = transitions.all? {|transition| transition != false}
|
124
|
+
# Clear any traces of the event since transitions are available and to
|
125
|
+
# prevent from being evaluated multiple times if actions are nested
|
126
|
+
transitions.each do |transition|
|
127
|
+
transition.machine.write(object, :event, nil)
|
128
|
+
transition.machine.write(object, :event_transition, nil)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Perform the transitions
|
120
132
|
begin
|
121
|
-
result = Transition.perform(transitions, :after => complete)
|
122
|
-
# Prevent events from being evaluated multiple times if actions are nested
|
123
|
-
transitions.each {|transition| transition.machine.write(object, :event, nil)}
|
124
|
-
action_value = yield
|
125
|
-
end
|
133
|
+
result = Transition.perform(transitions, :after => complete) { action_value = yield }
|
126
134
|
rescue Exception
|
127
|
-
#
|
135
|
+
# Reset the event attribute so it can be re-evaluated if attempted again
|
128
136
|
transitions.each do |transition|
|
129
137
|
transition.machine.write(object, :event, transition.event)
|
130
|
-
transition.machine.write(object, :event_transition, nil) if complete
|
131
138
|
end
|
132
139
|
|
133
140
|
raise
|
134
141
|
end
|
135
142
|
|
136
143
|
transitions.each do |transition|
|
137
|
-
# Revert event
|
138
|
-
transition.machine.write(object, :event, transition.event) unless
|
144
|
+
# Revert event if failed (to allow for more attempts)
|
145
|
+
transition.machine.write(object, :event, transition.event) unless result
|
139
146
|
|
140
|
-
# Track transition if partial transition
|
141
|
-
transition.machine.write(object, :event_transition, !complete && result
|
147
|
+
# Track transition if partial transition was successful
|
148
|
+
transition.machine.write(object, :event_transition, transition) if !complete && result
|
142
149
|
end
|
143
150
|
end
|
144
151
|
|
data/lib/state_machine/state.rb
CHANGED
@@ -168,7 +168,7 @@ module StateMachine
|
|
168
168
|
# Calls the method defined by the current state of the machine
|
169
169
|
context.class_eval <<-end_eval, __FILE__, __LINE__
|
170
170
|
def #{method}(*args, &block)
|
171
|
-
self.class.state_machine(#{machine_name.inspect}).states.match!(self).call(self, #{method.inspect}, *args, &block)
|
171
|
+
self.class.state_machine(#{machine_name.inspect}).states.match!(self).call(self, #{method.inspect}, lambda {super}, *args, &block)
|
172
172
|
end
|
173
173
|
end_eval
|
174
174
|
end
|
@@ -185,13 +185,13 @@ module StateMachine
|
|
185
185
|
#
|
186
186
|
# If the method has never been defined for this state, then a NoMethodError
|
187
187
|
# will be raised.
|
188
|
-
def call(object, method, *args, &block)
|
188
|
+
def call(object, method, method_missing = nil, *args, &block)
|
189
189
|
if context_method = methods[method.to_sym]
|
190
190
|
# Method is defined by the state: proxy it through
|
191
191
|
context_method.bind(object).call(*args, &block)
|
192
192
|
else
|
193
|
-
#
|
194
|
-
|
193
|
+
# Dispatch to the superclass since this state doesn't handle the method
|
194
|
+
method_missing.call if method_missing
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
File without changes
|
@@ -2,7 +2,7 @@ namespace :state_machine do
|
|
2
2
|
desc 'Draws a set of state machines using GraphViz. Target files to load with FILE=x,y,z; Machine class with CLASS=x,y,z; Font name with FONT=x; Image format with FORMAT=x; Orientation with ORIENTATION=x'
|
3
3
|
task :draw do
|
4
4
|
# Load the library
|
5
|
-
$:.unshift(File.dirname(__FILE__) + '
|
5
|
+
$:.unshift(File.dirname(__FILE__) + '/..')
|
6
6
|
require 'state_machine'
|
7
7
|
|
8
8
|
# Build drawing options
|
@@ -131,6 +131,10 @@ class Vehicle < ModelBase
|
|
131
131
|
auto_shop.fix_vehicle
|
132
132
|
end
|
133
133
|
|
134
|
+
def decibels
|
135
|
+
0.0
|
136
|
+
end
|
137
|
+
|
134
138
|
private
|
135
139
|
# Safety first! Puts on our seatbelt
|
136
140
|
def put_on_seatbelt
|
@@ -169,7 +173,13 @@ class Car < Vehicle
|
|
169
173
|
end
|
170
174
|
|
171
175
|
class Motorcycle < Vehicle
|
172
|
-
state_machine :initial => :idling
|
176
|
+
state_machine :initial => :idling do
|
177
|
+
state :first_gear do
|
178
|
+
def decibels
|
179
|
+
1.0
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
173
183
|
end
|
174
184
|
|
175
185
|
class TrafficLight
|
@@ -765,6 +775,16 @@ class MotorcycleTest < Test::Unit::TestCase
|
|
765
775
|
def test_should_not_allow_repair
|
766
776
|
assert !@motorcycle.repair
|
767
777
|
end
|
778
|
+
|
779
|
+
def test_should_inherit_decibels_from_superclass
|
780
|
+
@motorcycle.park
|
781
|
+
assert_equal 0.0, @motorcycle.decibels
|
782
|
+
end
|
783
|
+
|
784
|
+
def test_should_use_decibels_defined_in_state
|
785
|
+
@motorcycle.shift_up
|
786
|
+
assert_equal 1.0, @motorcycle.decibels
|
787
|
+
end
|
768
788
|
end
|
769
789
|
|
770
790
|
class CarTest < Test::Unit::TestCase
|
@@ -174,11 +174,20 @@ class EventCollectionAttributeWithMachineActionTest < Test::Unit::TestCase
|
|
174
174
|
end
|
175
175
|
|
176
176
|
def test_should_have_valid_transition_if_already_defined_in_transition_cache
|
177
|
+
@ignite.transition :parked => :idling
|
177
178
|
@object.state_event = nil
|
178
179
|
@object.send(:state_event_transition=, transition = @ignite.transition_for(@object))
|
179
180
|
|
180
181
|
assert_equal transition, @events.attribute_transition_for(@object)
|
181
182
|
end
|
183
|
+
|
184
|
+
def test_should_use_transition_cache_if_both_event_and_transition_are_present
|
185
|
+
@ignite.transition :parked => :idling
|
186
|
+
@object.state_event = 'ignite'
|
187
|
+
@object.send(:state_event_transition=, transition = @ignite.transition_for(@object))
|
188
|
+
|
189
|
+
assert_equal transition, @events.attribute_transition_for(@object)
|
190
|
+
end
|
182
191
|
end
|
183
192
|
|
184
193
|
class EventCollectionAttributeWithNamespacedMachineTest < Test::Unit::TestCase
|
data/test/unit/event_test.rb
CHANGED
@@ -663,6 +663,50 @@ class EventWithInvalidCurrentStateTest < Test::Unit::TestCase
|
|
663
663
|
end
|
664
664
|
end
|
665
665
|
|
666
|
+
class EventWithMarshallingTest < Test::Unit::TestCase
|
667
|
+
def setup
|
668
|
+
@klass = Class.new do
|
669
|
+
def save
|
670
|
+
true
|
671
|
+
end
|
672
|
+
end
|
673
|
+
self.class.const_set('Example', @klass)
|
674
|
+
|
675
|
+
@machine = StateMachine::Machine.new(@klass, :action => :save)
|
676
|
+
@machine.state :parked, :idling
|
677
|
+
|
678
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
679
|
+
@event.transition(:parked => :idling)
|
680
|
+
|
681
|
+
@object = @klass.new
|
682
|
+
@object.state = 'parked'
|
683
|
+
end
|
684
|
+
|
685
|
+
def test_should_marshal_during_before_callbacks
|
686
|
+
@machine.before_transition {|object, transition| Marshal.dump(object)}
|
687
|
+
assert_nothing_raised { @event.fire(@object) }
|
688
|
+
end
|
689
|
+
|
690
|
+
def test_should_marshal_during_action
|
691
|
+
@klass.class_eval do
|
692
|
+
def save
|
693
|
+
Marshal.dump(self)
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
assert_nothing_raised { @event.fire(@object) }
|
698
|
+
end
|
699
|
+
|
700
|
+
def test_should_marshal_during_after_callbacks
|
701
|
+
@machine.after_transition {|object, transition| Marshal.dump(object)}
|
702
|
+
assert_nothing_raised { @event.fire(@object) }
|
703
|
+
end
|
704
|
+
|
705
|
+
def teardown
|
706
|
+
self.class.send(:remove_const, 'Example')
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
666
710
|
begin
|
667
711
|
# Load library
|
668
712
|
require 'rubygems'
|
@@ -691,7 +735,7 @@ begin
|
|
691
735
|
end
|
692
736
|
|
693
737
|
def test_should_use_event_name_for_edge_label
|
694
|
-
assert_equal 'park', @edges.first['label']
|
738
|
+
assert_equal 'park', @edges.first['label'].to_s.gsub('"', '')
|
695
739
|
end
|
696
740
|
end
|
697
741
|
rescue LoadError
|
data/test/unit/guard_test.rb
CHANGED
@@ -4,7 +4,7 @@ begin
|
|
4
4
|
# Load library
|
5
5
|
require 'rubygems'
|
6
6
|
|
7
|
-
gem 'activerecord', ENV['AR_VERSION'] ? "=#{ENV['AR_VERSION']}" : '>=2.
|
7
|
+
gem 'activerecord', ENV['AR_VERSION'] ? "=#{ENV['AR_VERSION']}" : '>=2.0.0'
|
8
8
|
require 'active_record'
|
9
9
|
|
10
10
|
FIXTURES_ROOT = File.dirname(__FILE__) + '/../../fixtures/'
|
@@ -12,44 +12,59 @@ begin
|
|
12
12
|
# Load TestCase helpers
|
13
13
|
require 'active_support/test_case'
|
14
14
|
require 'active_record/fixtures'
|
15
|
-
|
15
|
+
|
16
|
+
require 'active_record/version'
|
17
|
+
if ActiveRecord::VERSION::STRING >= '2.1.0'
|
18
|
+
require 'active_record/test_case'
|
19
|
+
else
|
20
|
+
class ActiveRecord::TestCase < ActiveSupport::TestCase
|
21
|
+
self.fixture_path = FIXTURES_ROOT
|
22
|
+
self.use_instantiated_fixtures = false
|
23
|
+
self.use_transactional_fixtures = true
|
24
|
+
end
|
25
|
+
end
|
16
26
|
|
17
27
|
# Establish database connection
|
18
28
|
ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3', 'database' => ':memory:'})
|
19
29
|
ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../../active_record.log")
|
20
30
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
module ActiveRecordTest
|
32
|
+
class BaseTestCase < ActiveRecord::TestCase
|
33
|
+
def default_test
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
# Creates a new ActiveRecord model (and the associated table)
|
38
|
+
def new_model(create_table = :foo, &block)
|
39
|
+
table_name = create_table || :foo
|
40
|
+
|
41
|
+
model = Class.new(ActiveRecord::Base) do
|
42
|
+
connection.create_table(table_name, :force => true) {|t| t.string(:state)} if create_table
|
43
|
+
set_table_name(table_name.to_s)
|
44
|
+
|
45
|
+
def self.name; "ActiveRecordTest::#{table_name.capitalize}"; end
|
46
|
+
end
|
47
|
+
model.class_eval(&block) if block_given?
|
48
|
+
model
|
49
|
+
end
|
39
50
|
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
# Creates a new ActiveRecord observer
|
52
|
+
def new_observer(model, &block)
|
53
|
+
observer = Class.new(ActiveRecord::Observer) do
|
54
|
+
attr_accessor :notifications
|
55
|
+
|
56
|
+
def initialize
|
57
|
+
super
|
58
|
+
@notifications = []
|
59
|
+
end
|
60
|
+
end
|
61
|
+
observer.observe(model)
|
62
|
+
observer.class_eval(&block) if block_given?
|
63
|
+
observer
|
43
64
|
end
|
44
|
-
end
|
45
|
-
observer.observe(model)
|
46
|
-
observer.class_eval(&block) if block_given?
|
47
|
-
observer
|
48
65
|
end
|
49
|
-
|
50
|
-
|
51
|
-
module ActiveRecordTest
|
52
|
-
class IntegrationTest < ActiveRecord::TestCase
|
66
|
+
|
67
|
+
class IntegrationTest < BaseTestCase
|
53
68
|
def test_should_match_if_class_inherits_from_active_record
|
54
69
|
assert StateMachine::Integrations::ActiveRecord.matches?(new_model)
|
55
70
|
end
|
@@ -57,150 +72,13 @@ begin
|
|
57
72
|
def test_should_not_match_if_class_does_not_inherit_from_active_record
|
58
73
|
assert !StateMachine::Integrations::ActiveRecord.matches?(Class.new)
|
59
74
|
end
|
60
|
-
end
|
61
|
-
|
62
|
-
class MachineByDefaultTest < ActiveRecord::TestCase
|
63
|
-
def setup
|
64
|
-
@model = new_model
|
65
|
-
@machine = StateMachine::Machine.new(@model)
|
66
|
-
end
|
67
|
-
|
68
|
-
def test_should_use_save_as_action
|
69
|
-
assert_equal :save, @machine.action
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_should_use_transactions
|
73
|
-
assert_equal true, @machine.use_transactions
|
74
|
-
end
|
75
|
-
|
76
|
-
def test_should_create_notifier_before_callback
|
77
|
-
assert_equal 1, @machine.callbacks[:before].size
|
78
|
-
end
|
79
|
-
|
80
|
-
def test_should_create_notifier_after_callback
|
81
|
-
assert_equal 1, @machine.callbacks[:after].size
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class MachineTest < ActiveRecord::TestCase
|
86
|
-
def setup
|
87
|
-
@model = new_model
|
88
|
-
@machine = StateMachine::Machine.new(@model)
|
89
|
-
@machine.state :parked, :first_gear
|
90
|
-
@machine.state :idling, :value => lambda {'idling'}
|
91
|
-
end
|
92
|
-
|
93
|
-
def test_should_create_singular_with_scope
|
94
|
-
assert @model.respond_to?(:with_state)
|
95
|
-
end
|
96
|
-
|
97
|
-
def test_should_only_include_records_with_state_in_singular_with_scope
|
98
|
-
parked = @model.create :state => 'parked'
|
99
|
-
idling = @model.create :state => 'idling'
|
100
|
-
|
101
|
-
assert_equal [parked], @model.with_state(:parked)
|
102
|
-
end
|
103
|
-
|
104
|
-
def test_should_create_plural_with_scope
|
105
|
-
assert @model.respond_to?(:with_states)
|
106
|
-
end
|
107
|
-
|
108
|
-
def test_should_only_include_records_with_states_in_plural_with_scope
|
109
|
-
parked = @model.create :state => 'parked'
|
110
|
-
idling = @model.create :state => 'idling'
|
111
|
-
|
112
|
-
assert_equal [parked, idling], @model.with_states(:parked, :idling)
|
113
|
-
end
|
114
|
-
|
115
|
-
def test_should_create_singular_without_scope
|
116
|
-
assert @model.respond_to?(:without_state)
|
117
|
-
end
|
118
|
-
|
119
|
-
def test_should_only_include_records_without_state_in_singular_without_scope
|
120
|
-
parked = @model.create :state => 'parked'
|
121
|
-
idling = @model.create :state => 'idling'
|
122
|
-
|
123
|
-
assert_equal [parked], @model.without_state(:idling)
|
124
|
-
end
|
125
|
-
|
126
|
-
def test_should_create_plural_without_scope
|
127
|
-
assert @model.respond_to?(:without_states)
|
128
|
-
end
|
129
|
-
|
130
|
-
def test_should_only_include_records_without_states_in_plural_without_scope
|
131
|
-
parked = @model.create :state => 'parked'
|
132
|
-
idling = @model.create :state => 'idling'
|
133
|
-
first_gear = @model.create :state => 'first_gear'
|
134
|
-
|
135
|
-
assert_equal [parked, idling], @model.without_states(:first_gear)
|
136
|
-
end
|
137
|
-
|
138
|
-
def test_should_allow_chaining_scopes
|
139
|
-
parked = @model.create :state => 'parked'
|
140
|
-
idling = @model.create :state => 'idling'
|
141
|
-
|
142
|
-
assert_equal [idling], @model.without_state(:parked).with_state(:idling)
|
143
|
-
end
|
144
|
-
|
145
|
-
def test_should_rollback_transaction_if_false
|
146
|
-
@machine.within_transaction(@model.new) do
|
147
|
-
@model.create
|
148
|
-
false
|
149
|
-
end
|
150
|
-
|
151
|
-
assert_equal 0, @model.count
|
152
|
-
end
|
153
|
-
|
154
|
-
def test_should_not_rollback_transaction_if_true
|
155
|
-
@machine.within_transaction(@model.new) do
|
156
|
-
@model.create
|
157
|
-
true
|
158
|
-
end
|
159
|
-
|
160
|
-
assert_equal 1, @model.count
|
161
|
-
end
|
162
|
-
|
163
|
-
def test_should_invalidate_using_errors
|
164
|
-
I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n)
|
165
|
-
|
166
|
-
record = @model.new
|
167
|
-
record.state = 'parked'
|
168
|
-
|
169
|
-
@machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
|
170
|
-
|
171
|
-
assert_equal ['State cannot transition via "park"'], record.errors.full_messages
|
172
|
-
end
|
173
|
-
|
174
|
-
def test_should_auto_prefix_custom_attributes_on_invalidation
|
175
|
-
record = @model.new
|
176
|
-
@machine.invalidate(record, :event, :invalid)
|
177
|
-
|
178
|
-
assert_equal ['State event is invalid'], record.errors.full_messages
|
179
|
-
end
|
180
|
-
|
181
|
-
def test_should_clear_errors_on_reset
|
182
|
-
record = @model.new
|
183
|
-
record.state = 'parked'
|
184
|
-
record.errors.add(:state, 'is invalid')
|
185
|
-
|
186
|
-
@machine.reset(record)
|
187
|
-
assert_equal [], record.errors.full_messages
|
188
|
-
end
|
189
|
-
|
190
|
-
def test_should_not_override_the_column_reader
|
191
|
-
record = @model.new
|
192
|
-
record[:state] = 'parked'
|
193
|
-
assert_equal 'parked', record.state
|
194
|
-
end
|
195
75
|
|
196
|
-
def
|
197
|
-
|
198
|
-
record.state = 'parked'
|
199
|
-
assert_equal 'parked', record[:state]
|
76
|
+
def test_should_have_defaults
|
77
|
+
assert_equal e = {:action => :save}, StateMachine::Integrations::ActiveRecord.defaults
|
200
78
|
end
|
201
79
|
end
|
202
80
|
|
203
|
-
class MachineWithoutDatabaseTest <
|
81
|
+
class MachineWithoutDatabaseTest < BaseTestCase
|
204
82
|
def setup
|
205
83
|
@model = new_model(false) do
|
206
84
|
# Simulate the database not being available entirely
|
@@ -215,12 +93,12 @@ begin
|
|
215
93
|
end
|
216
94
|
end
|
217
95
|
|
218
|
-
class MachineUnmigratedTest <
|
96
|
+
class MachineUnmigratedTest < BaseTestCase
|
219
97
|
def setup
|
220
98
|
@model = new_model(false)
|
221
99
|
|
222
100
|
# Drop the table so that it definitely doesn't exist
|
223
|
-
@model.connection.drop_table(:foo) if @model.
|
101
|
+
@model.connection.drop_table(:foo) if @model.table_exists?
|
224
102
|
end
|
225
103
|
|
226
104
|
def test_should_allow_machine_creation
|
@@ -228,7 +106,30 @@ begin
|
|
228
106
|
end
|
229
107
|
end
|
230
108
|
|
231
|
-
class
|
109
|
+
class MachineByDefaultTest < BaseTestCase
|
110
|
+
def setup
|
111
|
+
@model = new_model
|
112
|
+
@machine = StateMachine::Machine.new(@model)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_should_use_save_as_action
|
116
|
+
assert_equal :save, @machine.action
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_should_use_transactions
|
120
|
+
assert_equal true, @machine.use_transactions
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_should_create_notifier_before_callback
|
124
|
+
assert_equal 1, @machine.callbacks[:before].size
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_should_create_notifier_after_callback
|
128
|
+
assert_equal 1, @machine.callbacks[:after].size
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class MachineWithStaticInitialStateTest < BaseTestCase
|
232
133
|
def setup
|
233
134
|
@model = new_model do
|
234
135
|
attr_accessor :value
|
@@ -241,6 +142,11 @@ begin
|
|
241
142
|
assert_equal 'parked', record.state
|
242
143
|
end
|
243
144
|
|
145
|
+
def test_should_set_initial_state_with_nil_attributes
|
146
|
+
record = @model.new(nil)
|
147
|
+
assert_equal 'parked', record.state
|
148
|
+
end
|
149
|
+
|
244
150
|
def test_should_still_set_attributes
|
245
151
|
record = @model.new(:value => 1)
|
246
152
|
assert_equal 1, record.value
|
@@ -257,10 +163,9 @@ begin
|
|
257
163
|
|
258
164
|
def test_should_set_attributes_prior_to_after_initialize_hook
|
259
165
|
state = nil
|
260
|
-
@model.class_eval
|
261
|
-
|
262
|
-
|
263
|
-
end
|
166
|
+
@model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
|
167
|
+
@model.after_initialize do |record|
|
168
|
+
state = record.state
|
264
169
|
end
|
265
170
|
@model.new
|
266
171
|
assert_equal 'parked', state
|
@@ -289,7 +194,7 @@ begin
|
|
289
194
|
end
|
290
195
|
end
|
291
196
|
|
292
|
-
class MachineWithDynamicInitialStateTest <
|
197
|
+
class MachineWithDynamicInitialStateTest < BaseTestCase
|
293
198
|
def setup
|
294
199
|
@model = new_model do
|
295
200
|
attr_accessor :value
|
@@ -319,10 +224,9 @@ begin
|
|
319
224
|
|
320
225
|
def test_should_set_attributes_prior_to_after_initialize_hook
|
321
226
|
state = nil
|
322
|
-
@model.class_eval
|
323
|
-
|
324
|
-
|
325
|
-
end
|
227
|
+
@model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
|
228
|
+
@model.after_initialize do |record|
|
229
|
+
state = record.state
|
326
230
|
end
|
327
231
|
@model.new
|
328
232
|
assert_equal 'parked', state
|
@@ -351,7 +255,21 @@ begin
|
|
351
255
|
end
|
352
256
|
end
|
353
257
|
|
354
|
-
class
|
258
|
+
class MachineWithColumnDefaultTest < BaseTestCase
|
259
|
+
def setup
|
260
|
+
@model = new_model do
|
261
|
+
connection.add_column :foo, :status, :string, :default => 'idling'
|
262
|
+
end
|
263
|
+
@machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
|
264
|
+
@record = @model.new
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_should_use_machine_default
|
268
|
+
assert_equal 'parked', @record.status
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
class MachineWithConflictingPredicateTest < BaseTestCase
|
355
273
|
def setup
|
356
274
|
@model = new_model do
|
357
275
|
def state?(*args)
|
@@ -368,7 +286,7 @@ begin
|
|
368
286
|
end
|
369
287
|
end
|
370
288
|
|
371
|
-
class MachineWithColumnStateAttributeTest <
|
289
|
+
class MachineWithColumnStateAttributeTest < BaseTestCase
|
372
290
|
def setup
|
373
291
|
@model = new_model
|
374
292
|
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
@@ -377,6 +295,16 @@ begin
|
|
377
295
|
@record = @model.new
|
378
296
|
end
|
379
297
|
|
298
|
+
def test_should_not_override_the_column_reader
|
299
|
+
@record[:state] = 'parked'
|
300
|
+
assert_equal 'parked', @record.state
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_should_not_override_the_column_writer
|
304
|
+
@record.state = 'parked'
|
305
|
+
assert_equal 'parked', @record[:state]
|
306
|
+
end
|
307
|
+
|
380
308
|
def test_should_have_an_attribute_predicate
|
381
309
|
assert @record.respond_to?(:state?)
|
382
310
|
end
|
@@ -401,11 +329,13 @@ begin
|
|
401
329
|
end
|
402
330
|
end
|
403
331
|
|
404
|
-
class MachineWithNonColumnStateAttributeUndefinedTest <
|
332
|
+
class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
|
405
333
|
def setup
|
406
334
|
@model = new_model do
|
407
335
|
def initialize
|
408
336
|
# Skip attribute initialization
|
337
|
+
@initialized_state_machines = true
|
338
|
+
super
|
409
339
|
end
|
410
340
|
end
|
411
341
|
|
@@ -434,7 +364,7 @@ begin
|
|
434
364
|
end
|
435
365
|
end
|
436
366
|
|
437
|
-
class MachineWithNonColumnStateAttributeDefinedTest <
|
367
|
+
class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
|
438
368
|
def setup
|
439
369
|
@model = new_model do
|
440
370
|
attr_accessor :status
|
@@ -462,79 +392,244 @@ begin
|
|
462
392
|
end
|
463
393
|
end
|
464
394
|
|
465
|
-
class
|
395
|
+
class MachineWithAliasedAttributeTest < BaseTestCase
|
396
|
+
def setup
|
397
|
+
@model = new_model do
|
398
|
+
alias_attribute :vehicle_status, :state
|
399
|
+
end
|
400
|
+
|
401
|
+
@machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
|
402
|
+
@machine.state :parked
|
403
|
+
|
404
|
+
@record = @model.new
|
405
|
+
end
|
406
|
+
|
407
|
+
def test_should_check_custom_attribute_for_predicate
|
408
|
+
@record.vehicle_status = nil
|
409
|
+
assert !@record.status?(:parked)
|
410
|
+
|
411
|
+
@record.vehicle_status = 'parked'
|
412
|
+
assert @record.status?(:parked)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
class MachineWithInitializedStateTest < BaseTestCase
|
466
417
|
def setup
|
467
418
|
@model = new_model
|
468
|
-
@machine = StateMachine::Machine.new(@model, :
|
419
|
+
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
420
|
+
@machine.state nil, :idling
|
421
|
+
end
|
422
|
+
|
423
|
+
def test_should_allow_nil_initial_state_when_static
|
424
|
+
record = @model.new(:state => nil)
|
425
|
+
assert_nil record.state
|
426
|
+
end
|
427
|
+
|
428
|
+
def test_should_allow_nil_initial_state_when_dynamic
|
429
|
+
@machine.initial_state = lambda {:parked}
|
430
|
+
record = @model.new(:state => nil)
|
431
|
+
assert_nil record.state
|
469
432
|
end
|
470
433
|
|
471
|
-
def
|
472
|
-
|
434
|
+
def test_should_allow_different_initial_state_when_static
|
435
|
+
record = @model.new(:state => 'idling')
|
436
|
+
assert_equal 'idling', record.state
|
437
|
+
end
|
438
|
+
|
439
|
+
def test_should_allow_different_initial_state_when_dynamic
|
440
|
+
@machine.initial_state = lambda {:parked}
|
441
|
+
record = @model.new(:state => 'idling')
|
442
|
+
assert_equal 'idling', record.state
|
473
443
|
end
|
474
444
|
|
475
|
-
def
|
476
|
-
|
445
|
+
def test_should_use_default_state_if_protected
|
446
|
+
@model.class_eval do
|
447
|
+
attr_protected :state
|
448
|
+
end
|
449
|
+
|
450
|
+
record = @model.new(:state => 'idling')
|
451
|
+
assert_equal 'parked', record.state
|
477
452
|
end
|
478
453
|
end
|
479
454
|
|
480
|
-
class
|
455
|
+
class MachineWithLoopbackTest < BaseTestCase
|
481
456
|
def setup
|
482
|
-
@model = new_model
|
483
|
-
|
457
|
+
@model = new_model do
|
458
|
+
connection.add_column :foo, :updated_at, :datetime
|
459
|
+
end
|
460
|
+
|
461
|
+
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
462
|
+
@machine.event :park
|
463
|
+
|
464
|
+
@record = @model.create(:updated_at => Time.now - 1)
|
465
|
+
@transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
|
466
|
+
|
467
|
+
@timestamp = @record.updated_at
|
468
|
+
@transition.perform
|
469
|
+
end
|
470
|
+
|
471
|
+
def test_should_update_record
|
472
|
+
assert_not_equal @timestamp, @record.updated_at
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty)
|
477
|
+
class MachineWithDirtyAttributesTest < BaseTestCase
|
478
|
+
def setup
|
479
|
+
@model = new_model
|
480
|
+
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
481
|
+
@machine.event :ignite
|
482
|
+
@machine.state :idling
|
483
|
+
|
484
|
+
@record = @model.create
|
485
|
+
|
486
|
+
@transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
|
487
|
+
@transition.perform(false)
|
488
|
+
end
|
489
|
+
|
490
|
+
def test_should_include_state_in_changed_attributes
|
491
|
+
assert_equal %w(state), @record.changed
|
492
|
+
end
|
493
|
+
|
494
|
+
def test_should_track_attribute_change
|
495
|
+
assert_equal %w(parked idling), @record.changes['state']
|
496
|
+
end
|
497
|
+
|
498
|
+
def test_should_not_reset_changes_on_multiple_transitions
|
499
|
+
transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
|
500
|
+
transition.perform(false)
|
501
|
+
|
502
|
+
assert_equal %w(parked idling), @record.changes['state']
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
|
507
|
+
def setup
|
508
|
+
@model = new_model
|
509
|
+
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
510
|
+
@machine.event :park
|
511
|
+
|
512
|
+
@record = @model.create
|
513
|
+
|
514
|
+
@transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
|
515
|
+
@transition.perform(false)
|
516
|
+
end
|
517
|
+
|
518
|
+
def test_should_include_state_in_changed_attributes
|
519
|
+
assert_equal %w(state), @record.changed
|
520
|
+
end
|
484
521
|
|
485
|
-
|
486
|
-
|
487
|
-
|
522
|
+
def test_should_track_attribute_changes
|
523
|
+
assert_equal %w(parked parked), @record.changes['state']
|
524
|
+
end
|
488
525
|
end
|
489
526
|
|
490
|
-
|
491
|
-
|
492
|
-
|
527
|
+
class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
|
528
|
+
def setup
|
529
|
+
@model = new_model do
|
530
|
+
connection.add_column :foo, :status, :string, :default => 'idling'
|
531
|
+
end
|
532
|
+
@machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
|
533
|
+
@machine.event :ignite
|
534
|
+
@machine.state :idling
|
535
|
+
|
536
|
+
@record = @model.create
|
537
|
+
|
538
|
+
@transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
|
539
|
+
@transition.perform(false)
|
540
|
+
end
|
541
|
+
|
542
|
+
def test_should_include_state_in_changed_attributes
|
543
|
+
assert_equal %w(status), @record.changed
|
544
|
+
end
|
545
|
+
|
546
|
+
def test_should_track_attribute_change
|
547
|
+
assert_equal %w(parked idling), @record.changes['status']
|
548
|
+
end
|
493
549
|
|
494
|
-
|
550
|
+
def test_should_not_reset_changes_on_multiple_transitions
|
551
|
+
transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
|
552
|
+
transition.perform(false)
|
553
|
+
|
554
|
+
assert_equal %w(parked idling), @record.changes['status']
|
555
|
+
end
|
495
556
|
end
|
496
557
|
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
558
|
+
class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
|
559
|
+
def setup
|
560
|
+
@model = new_model do
|
561
|
+
connection.add_column :foo, :status, :string, :default => 'idling'
|
562
|
+
end
|
563
|
+
@machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
|
564
|
+
@machine.event :park
|
565
|
+
|
566
|
+
@record = @model.create
|
567
|
+
|
568
|
+
@transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
|
569
|
+
@transition.perform(false)
|
570
|
+
end
|
571
|
+
|
572
|
+
def test_should_include_state_in_changed_attributes
|
573
|
+
assert_equal %w(status), @record.changed
|
574
|
+
end
|
501
575
|
|
502
|
-
|
576
|
+
def test_should_track_attribute_changes
|
577
|
+
assert_equal %w(parked parked), @record.changes['status']
|
578
|
+
end
|
503
579
|
end
|
504
580
|
end
|
505
581
|
|
506
|
-
class
|
582
|
+
class MachineWithoutTransactionsTest < BaseTestCase
|
507
583
|
def setup
|
508
|
-
@model = new_model
|
509
|
-
|
584
|
+
@model = new_model
|
585
|
+
@machine = StateMachine::Machine.new(@model, :use_transactions => false)
|
586
|
+
end
|
587
|
+
|
588
|
+
def test_should_not_rollback_transaction_if_false
|
589
|
+
@machine.within_transaction(@model.new) do
|
590
|
+
@model.create
|
591
|
+
false
|
510
592
|
end
|
511
593
|
|
512
|
-
|
513
|
-
@machine.state :parked
|
514
|
-
|
515
|
-
@record = @model.new
|
594
|
+
assert_equal 1, @model.count
|
516
595
|
end
|
517
596
|
|
518
|
-
def
|
519
|
-
@
|
597
|
+
def test_should_not_rollback_transaction_if_true
|
598
|
+
@machine.within_transaction(@model.new) do
|
599
|
+
@model.create
|
600
|
+
true
|
601
|
+
end
|
520
602
|
|
521
|
-
|
522
|
-
|
603
|
+
assert_equal 1, @model.count
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
class MachineWithTransactionsTest < BaseTestCase
|
608
|
+
def setup
|
609
|
+
@model = new_model
|
610
|
+
@machine = StateMachine::Machine.new(@model, :use_transactions => true)
|
611
|
+
end
|
612
|
+
|
613
|
+
def test_should_rollback_transaction_if_false
|
614
|
+
@machine.within_transaction(@model.new) do
|
615
|
+
@model.create
|
616
|
+
false
|
617
|
+
end
|
523
618
|
|
524
|
-
@
|
525
|
-
assert @record.valid?
|
619
|
+
assert_equal 0, @model.count
|
526
620
|
end
|
527
621
|
|
528
|
-
def
|
529
|
-
@
|
530
|
-
|
622
|
+
def test_should_not_rollback_transaction_if_true
|
623
|
+
@machine.within_transaction(@model.new) do
|
624
|
+
@model.create
|
625
|
+
true
|
626
|
+
end
|
531
627
|
|
532
|
-
@
|
533
|
-
assert @record.status?(:parked)
|
628
|
+
assert_equal 1, @model.count
|
534
629
|
end
|
535
630
|
end
|
536
631
|
|
537
|
-
class MachineWithCallbacksTest <
|
632
|
+
class MachineWithCallbacksTest < BaseTestCase
|
538
633
|
def setup
|
539
634
|
@model = new_model
|
540
635
|
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
@@ -641,43 +736,7 @@ begin
|
|
641
736
|
end
|
642
737
|
end
|
643
738
|
|
644
|
-
class
|
645
|
-
def setup
|
646
|
-
changed_attrs = nil
|
647
|
-
|
648
|
-
@model = new_model do
|
649
|
-
connection.change_table(:foo) {|t| t.datetime(:updated_at)}
|
650
|
-
|
651
|
-
define_method(:before_update) do
|
652
|
-
changed_attrs = changed_attributes.dup
|
653
|
-
end
|
654
|
-
end
|
655
|
-
|
656
|
-
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
657
|
-
@machine.event :park
|
658
|
-
|
659
|
-
@record = @model.create(:updated_at => Time.now - 1)
|
660
|
-
@timestamp = @record.updated_at
|
661
|
-
|
662
|
-
@transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
|
663
|
-
@transition.perform
|
664
|
-
|
665
|
-
@changed_attrs = changed_attrs
|
666
|
-
end
|
667
|
-
|
668
|
-
def test_should_include_state_in_changed_attributes
|
669
|
-
@changed_attrs.delete('updated_at')
|
670
|
-
|
671
|
-
expected = {'state' => 'parked'}
|
672
|
-
assert_equal expected, @changed_attrs
|
673
|
-
end
|
674
|
-
|
675
|
-
def test_should_update_record
|
676
|
-
assert_not_equal @timestamp, @record.updated_at
|
677
|
-
end
|
678
|
-
end
|
679
|
-
|
680
|
-
class MachineWithFailedBeforeCallbacksTest < ActiveRecord::TestCase
|
739
|
+
class MachineWithFailedBeforeCallbacksTest < BaseTestCase
|
681
740
|
def setup
|
682
741
|
@before_count = 0
|
683
742
|
@after_count = 0
|
@@ -716,7 +775,7 @@ begin
|
|
716
775
|
end
|
717
776
|
end
|
718
777
|
|
719
|
-
class MachineWithFailedActionTest <
|
778
|
+
class MachineWithFailedActionTest < BaseTestCase
|
720
779
|
def setup
|
721
780
|
@model = new_model do
|
722
781
|
validates_inclusion_of :state, :in => %w(first_gear)
|
@@ -763,7 +822,40 @@ begin
|
|
763
822
|
end
|
764
823
|
end
|
765
824
|
|
766
|
-
class
|
825
|
+
class MachineWithFailedAfterCallbacksTest < BaseTestCase
|
826
|
+
def setup
|
827
|
+
@after_count = 0
|
828
|
+
|
829
|
+
@model = new_model
|
830
|
+
@machine = StateMachine::Machine.new(@model)
|
831
|
+
@machine.state :parked, :idling
|
832
|
+
@machine.event :ignite
|
833
|
+
@machine.after_transition(lambda {@after_count += 1; false})
|
834
|
+
@machine.after_transition(lambda {@after_count += 1})
|
835
|
+
|
836
|
+
@record = @model.new(:state => 'parked')
|
837
|
+
@transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
|
838
|
+
@result = @transition.perform
|
839
|
+
end
|
840
|
+
|
841
|
+
def test_should_be_successful
|
842
|
+
assert @result
|
843
|
+
end
|
844
|
+
|
845
|
+
def test_should_change_current_state
|
846
|
+
assert_equal 'idling', @record.state
|
847
|
+
end
|
848
|
+
|
849
|
+
def test_should_save_record
|
850
|
+
assert !@record.new_record?
|
851
|
+
end
|
852
|
+
|
853
|
+
def test_should_not_run_further_after_callbacks
|
854
|
+
assert_equal 1, @after_count
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
class MachineWithValidationsTest < BaseTestCase
|
767
859
|
def setup
|
768
860
|
@model = new_model
|
769
861
|
@machine = StateMachine::Machine.new(@model)
|
@@ -772,6 +864,28 @@ begin
|
|
772
864
|
@record = @model.new
|
773
865
|
end
|
774
866
|
|
867
|
+
def test_should_invalidate_using_errors
|
868
|
+
I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n)
|
869
|
+
@record.state = 'parked'
|
870
|
+
|
871
|
+
@machine.invalidate(@record, :state, :invalid_transition, [[:event, :park]])
|
872
|
+
assert_equal ['State cannot transition via "park"'], @record.errors.full_messages
|
873
|
+
end
|
874
|
+
|
875
|
+
def test_should_auto_prefix_custom_attributes_on_invalidation
|
876
|
+
@machine.invalidate(@record, :event, :invalid)
|
877
|
+
|
878
|
+
assert_equal ['State event is invalid'], @record.errors.full_messages
|
879
|
+
end
|
880
|
+
|
881
|
+
def test_should_clear_errors_on_reset
|
882
|
+
@record.state = 'parked'
|
883
|
+
@record.errors.add(:state, 'is invalid')
|
884
|
+
|
885
|
+
@machine.reset(@record)
|
886
|
+
assert_equal [], @record.errors.full_messages
|
887
|
+
end
|
888
|
+
|
775
889
|
def test_should_be_valid_if_state_is_known
|
776
890
|
@record.state = 'parked'
|
777
891
|
|
@@ -786,7 +900,30 @@ begin
|
|
786
900
|
end
|
787
901
|
end
|
788
902
|
|
789
|
-
class
|
903
|
+
class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
|
904
|
+
def setup
|
905
|
+
@model = new_model do
|
906
|
+
alias_attribute :status, :state
|
907
|
+
end
|
908
|
+
|
909
|
+
@machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
|
910
|
+
@machine.state :parked
|
911
|
+
|
912
|
+
@record = @model.new
|
913
|
+
end
|
914
|
+
|
915
|
+
def test_should_add_validation_errors_to_custom_attribute
|
916
|
+
@record.state = 'invalid'
|
917
|
+
|
918
|
+
assert !@record.valid?
|
919
|
+
assert_equal ['State is invalid'], @record.errors.full_messages
|
920
|
+
|
921
|
+
@record.state = 'parked'
|
922
|
+
assert @record.valid?
|
923
|
+
end
|
924
|
+
end
|
925
|
+
|
926
|
+
class MachineWithStateDrivenValidationsTest < BaseTestCase
|
790
927
|
def setup
|
791
928
|
@model = new_model do
|
792
929
|
attr_accessor :seatbelt
|
@@ -815,40 +952,7 @@ begin
|
|
815
952
|
end
|
816
953
|
end
|
817
954
|
|
818
|
-
class
|
819
|
-
def setup
|
820
|
-
@after_count = 0
|
821
|
-
|
822
|
-
@model = new_model
|
823
|
-
@machine = StateMachine::Machine.new(@model)
|
824
|
-
@machine.state :parked, :idling
|
825
|
-
@machine.event :ignite
|
826
|
-
@machine.after_transition(lambda {@after_count += 1; false})
|
827
|
-
@machine.after_transition(lambda {@after_count += 1})
|
828
|
-
|
829
|
-
@record = @model.new(:state => 'parked')
|
830
|
-
@transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
|
831
|
-
@result = @transition.perform
|
832
|
-
end
|
833
|
-
|
834
|
-
def test_should_be_successful
|
835
|
-
assert @result
|
836
|
-
end
|
837
|
-
|
838
|
-
def test_should_change_current_state
|
839
|
-
assert_equal 'idling', @record.state
|
840
|
-
end
|
841
|
-
|
842
|
-
def test_should_save_record
|
843
|
-
assert !@record.new_record?
|
844
|
-
end
|
845
|
-
|
846
|
-
def test_should_not_run_further_after_callbacks
|
847
|
-
assert_equal 1, @after_count
|
848
|
-
end
|
849
|
-
end
|
850
|
-
|
851
|
-
class MachineWithEventAttributesOnValidationTest < ActiveRecord::TestCase
|
955
|
+
class MachineWithEventAttributesOnValidationTest < BaseTestCase
|
852
956
|
def setup
|
853
957
|
@model = new_model
|
854
958
|
@machine = StateMachine::Machine.new(@model)
|
@@ -925,7 +1029,7 @@ begin
|
|
925
1029
|
end
|
926
1030
|
end
|
927
1031
|
|
928
|
-
class MachineWithEventAttributesOnSaveBangTest <
|
1032
|
+
class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
|
929
1033
|
def setup
|
930
1034
|
@model = new_model
|
931
1035
|
@machine = StateMachine::Machine.new(@model)
|
@@ -1002,7 +1106,7 @@ begin
|
|
1002
1106
|
end
|
1003
1107
|
end
|
1004
1108
|
|
1005
|
-
class MachineWithEventAttributesOnCustomActionTest <
|
1109
|
+
class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
|
1006
1110
|
def setup
|
1007
1111
|
@superclass = new_model do
|
1008
1112
|
def persist
|
@@ -1041,7 +1145,7 @@ begin
|
|
1041
1145
|
end
|
1042
1146
|
end
|
1043
1147
|
|
1044
|
-
class MachineWithObserversTest <
|
1148
|
+
class MachineWithObserversTest < BaseTestCase
|
1045
1149
|
def setup
|
1046
1150
|
@model = new_model
|
1047
1151
|
@machine = StateMachine::Machine.new(@model)
|
@@ -1114,9 +1218,29 @@ begin
|
|
1114
1218
|
@transition.perform
|
1115
1219
|
assert_equal [instance], instance.notifications
|
1116
1220
|
end
|
1221
|
+
|
1222
|
+
def test_should_use_original_observer_behavior_to_handle_non_state_machine_callbacks
|
1223
|
+
observer = new_observer(@model) do
|
1224
|
+
def before_save(object)
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
def before_ignite(*args)
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
def update_without_multiple_args(observed_method, object)
|
1231
|
+
notifications << [observed_method, object] if [:before_save, :before_ignite].include?(observed_method)
|
1232
|
+
super
|
1233
|
+
end
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
instance = observer.instance
|
1237
|
+
|
1238
|
+
@transition.perform
|
1239
|
+
assert_equal [[:before_save, @record]], instance.notifications
|
1240
|
+
end
|
1117
1241
|
end
|
1118
1242
|
|
1119
|
-
class MachineWithNamespacedObserversTest <
|
1243
|
+
class MachineWithNamespacedObserversTest < BaseTestCase
|
1120
1244
|
def setup
|
1121
1245
|
@model = new_model
|
1122
1246
|
@machine = StateMachine::Machine.new(@model, :state, :namespace => 'alarm')
|
@@ -1151,7 +1275,7 @@ begin
|
|
1151
1275
|
end
|
1152
1276
|
end
|
1153
1277
|
|
1154
|
-
class MachineWithMixedCallbacksTest <
|
1278
|
+
class MachineWithMixedCallbacksTest < BaseTestCase
|
1155
1279
|
def setup
|
1156
1280
|
@model = new_model
|
1157
1281
|
@machine = StateMachine::Machine.new(@model)
|
@@ -1204,8 +1328,147 @@ begin
|
|
1204
1328
|
end
|
1205
1329
|
end
|
1206
1330
|
|
1331
|
+
if ActiveRecord.const_defined?(:NamedScope)
|
1332
|
+
class MachineWithScopesTest < BaseTestCase
|
1333
|
+
def setup
|
1334
|
+
@model = new_model
|
1335
|
+
@machine = StateMachine::Machine.new(@model)
|
1336
|
+
@machine.state :parked, :first_gear
|
1337
|
+
@machine.state :idling, :value => lambda {'idling'}
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
def test_should_create_singular_with_scope
|
1341
|
+
assert @model.respond_to?(:with_state)
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
def test_should_only_include_records_with_state_in_singular_with_scope
|
1345
|
+
parked = @model.create :state => 'parked'
|
1346
|
+
idling = @model.create :state => 'idling'
|
1347
|
+
|
1348
|
+
assert_equal [parked], @model.with_state(:parked).find(:all)
|
1349
|
+
end
|
1350
|
+
|
1351
|
+
def test_should_create_plural_with_scope
|
1352
|
+
assert @model.respond_to?(:with_states)
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
def test_should_only_include_records_with_states_in_plural_with_scope
|
1356
|
+
parked = @model.create :state => 'parked'
|
1357
|
+
idling = @model.create :state => 'idling'
|
1358
|
+
|
1359
|
+
assert_equal [parked, idling], @model.with_states(:parked, :idling).find(:all)
|
1360
|
+
end
|
1361
|
+
|
1362
|
+
def test_should_create_singular_without_scope
|
1363
|
+
assert @model.respond_to?(:without_state)
|
1364
|
+
end
|
1365
|
+
|
1366
|
+
def test_should_only_include_records_without_state_in_singular_without_scope
|
1367
|
+
parked = @model.create :state => 'parked'
|
1368
|
+
idling = @model.create :state => 'idling'
|
1369
|
+
|
1370
|
+
assert_equal [parked], @model.without_state(:idling).find(:all)
|
1371
|
+
end
|
1372
|
+
|
1373
|
+
def test_should_create_plural_without_scope
|
1374
|
+
assert @model.respond_to?(:without_states)
|
1375
|
+
end
|
1376
|
+
|
1377
|
+
def test_should_only_include_records_without_states_in_plural_without_scope
|
1378
|
+
parked = @model.create :state => 'parked'
|
1379
|
+
idling = @model.create :state => 'idling'
|
1380
|
+
first_gear = @model.create :state => 'first_gear'
|
1381
|
+
|
1382
|
+
assert_equal [parked, idling], @model.without_states(:first_gear).find(:all)
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
def test_should_allow_chaining_scopes
|
1386
|
+
parked = @model.create :state => 'parked'
|
1387
|
+
idling = @model.create :state => 'idling'
|
1388
|
+
|
1389
|
+
assert_equal [idling], @model.without_state(:parked).with_state(:idling).find(:all)
|
1390
|
+
end
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
|
1394
|
+
def setup
|
1395
|
+
@model = new_model
|
1396
|
+
@machine = StateMachine::Machine.new(@model, :state)
|
1397
|
+
|
1398
|
+
@subclass = Class.new(@model)
|
1399
|
+
@subclass_machine = @subclass.state_machine(:state) {}
|
1400
|
+
@subclass_machine.state :parked, :idling, :first_gear
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
def test_should_only_include_records_with_subclass_states_in_with_scope
|
1404
|
+
parked = @subclass.create :state => 'parked'
|
1405
|
+
idling = @subclass.create :state => 'idling'
|
1406
|
+
|
1407
|
+
assert_equal [parked, idling], @subclass.with_states(:parked, :idling).find(:all)
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
def test_should_only_include_records_without_subclass_states_in_without_scope
|
1411
|
+
parked = @subclass.create :state => 'parked'
|
1412
|
+
idling = @subclass.create :state => 'idling'
|
1413
|
+
first_gear = @subclass.create :state => 'first_gear'
|
1414
|
+
|
1415
|
+
assert_equal [parked, idling], @subclass.without_states(:first_gear).find(:all)
|
1416
|
+
end
|
1417
|
+
end
|
1418
|
+
|
1419
|
+
class MachineWithComplexPluralizationScopesTest < BaseTestCase
|
1420
|
+
def setup
|
1421
|
+
@model = new_model
|
1422
|
+
@machine = StateMachine::Machine.new(@model, :status)
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
def test_should_create_singular_with_scope
|
1426
|
+
assert @model.respond_to?(:with_status)
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
def test_should_create_plural_with_scope
|
1430
|
+
assert @model.respond_to?(:with_statuses)
|
1431
|
+
end
|
1432
|
+
end
|
1433
|
+
|
1434
|
+
class MachineWithScopesAndJoinsTest < BaseTestCase
|
1435
|
+
def setup
|
1436
|
+
@company = new_model(:company)
|
1437
|
+
ActiveRecordTest.const_set('Company', @company)
|
1438
|
+
|
1439
|
+
@vehicle = new_model(:vehicle) do
|
1440
|
+
connection.add_column :vehicle, :company_id, :integer
|
1441
|
+
belongs_to :company, :class_name => 'ActiveRecordTest::Company'
|
1442
|
+
end
|
1443
|
+
ActiveRecordTest.const_set('Vehicle', @vehicle)
|
1444
|
+
|
1445
|
+
@company_machine = StateMachine::Machine.new(@company, :initial => :active)
|
1446
|
+
@vehicle_machine = StateMachine::Machine.new(@vehicle, :initial => :parked)
|
1447
|
+
@vehicle_machine.state :idling
|
1448
|
+
|
1449
|
+
@ford = @company.create
|
1450
|
+
@mustang = @vehicle.create(:company => @ford)
|
1451
|
+
end
|
1452
|
+
|
1453
|
+
def test_should_find_records_in_with_scope
|
1454
|
+
assert_equal [@mustang], @vehicle.with_states(:parked).find(:all, :include => :company, :conditions => 'company.state = "active"')
|
1455
|
+
end
|
1456
|
+
|
1457
|
+
def test_should_find_records_in_without_scope
|
1458
|
+
assert_equal [@mustang], @vehicle.without_states(:idling).find(:all, :include => :company, :conditions => 'company.state = "active"')
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
def teardown
|
1462
|
+
ActiveRecordTest.class_eval do
|
1463
|
+
remove_const('Vehicle')
|
1464
|
+
remove_const('Company')
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
|
1207
1470
|
if Object.const_defined?(:I18n)
|
1208
|
-
class MachineWithInternationalizationTest <
|
1471
|
+
class MachineWithInternationalizationTest < BaseTestCase
|
1209
1472
|
def setup
|
1210
1473
|
I18n.backend = I18n::Backend::Simple.new
|
1211
1474
|
|
@@ -1215,20 +1478,14 @@ begin
|
|
1215
1478
|
@model = new_model
|
1216
1479
|
end
|
1217
1480
|
|
1218
|
-
def
|
1481
|
+
def test_should_use_defaults
|
1219
1482
|
I18n.backend.store_translations(:en, {
|
1220
|
-
:activerecord => {
|
1221
|
-
:errors => {
|
1222
|
-
:messages => {
|
1223
|
-
:invalid_transition => 'cannot {{event}}'
|
1224
|
-
}
|
1225
|
-
}
|
1226
|
-
}
|
1483
|
+
:activerecord => {:errors => {:messages => {:invalid_transition => 'cannot {{event}}'}}}
|
1227
1484
|
})
|
1228
1485
|
|
1229
1486
|
machine = StateMachine::Machine.new(@model)
|
1230
1487
|
machine.state :parked, :idling
|
1231
|
-
event
|
1488
|
+
machine.event :ignite
|
1232
1489
|
|
1233
1490
|
record = @model.new(:state => 'idling')
|
1234
1491
|
|
@@ -1236,15 +1493,9 @@ begin
|
|
1236
1493
|
assert_equal ['State cannot ignite'], record.errors.full_messages
|
1237
1494
|
end
|
1238
1495
|
|
1239
|
-
def
|
1496
|
+
def test_should_allow_customized_error_key
|
1240
1497
|
I18n.backend.store_translations(:en, {
|
1241
|
-
:activerecord => {
|
1242
|
-
:errors => {
|
1243
|
-
:messages => {
|
1244
|
-
:bad_transition => 'cannot {{event}}'
|
1245
|
-
}
|
1246
|
-
}
|
1247
|
-
}
|
1498
|
+
:activerecord => {:errors => {:messages => {:bad_transition => 'cannot {{event}}'}}}
|
1248
1499
|
})
|
1249
1500
|
|
1250
1501
|
machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
|
@@ -1256,7 +1507,7 @@ begin
|
|
1256
1507
|
assert_equal ['State cannot ignite'], record.errors.full_messages
|
1257
1508
|
end
|
1258
1509
|
|
1259
|
-
def
|
1510
|
+
def test_should_allow_customized_error_string
|
1260
1511
|
machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot {{event}}'})
|
1261
1512
|
machine.state :parked, :idling
|
1262
1513
|
|
@@ -1266,6 +1517,81 @@ begin
|
|
1266
1517
|
assert_equal ['State cannot ignite'], record.errors.full_messages
|
1267
1518
|
end
|
1268
1519
|
|
1520
|
+
def test_should_allow_customized_state_key_scoped_to_class_and_machine
|
1521
|
+
I18n.backend.store_translations(:en, {
|
1522
|
+
:activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
|
1523
|
+
})
|
1524
|
+
|
1525
|
+
machine = StateMachine::Machine.new(@model, :initial => :parked)
|
1526
|
+
record = @model.new
|
1527
|
+
|
1528
|
+
machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
|
1529
|
+
assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
|
1530
|
+
end
|
1531
|
+
|
1532
|
+
def test_should_allow_customized_state_key_scoped_to_machine
|
1533
|
+
I18n.backend.store_translations(:en, {
|
1534
|
+
:activerecord => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
|
1535
|
+
})
|
1536
|
+
|
1537
|
+
machine = StateMachine::Machine.new(@model, :initial => :parked)
|
1538
|
+
record = @model.new
|
1539
|
+
|
1540
|
+
machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
|
1541
|
+
assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
|
1542
|
+
end
|
1543
|
+
|
1544
|
+
def test_should_allow_customized_state_key_unscoped
|
1545
|
+
I18n.backend.store_translations(:en, {
|
1546
|
+
:activerecord => {:state_machines => {:states => {:parked => 'shutdown'}}}
|
1547
|
+
})
|
1548
|
+
|
1549
|
+
machine = StateMachine::Machine.new(@model, :initial => :parked)
|
1550
|
+
record = @model.new
|
1551
|
+
|
1552
|
+
machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
|
1553
|
+
assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
|
1554
|
+
end
|
1555
|
+
|
1556
|
+
def test_should_allow_customized_event_key_scoped_to_class_and_machine
|
1557
|
+
I18n.backend.store_translations(:en, {
|
1558
|
+
:activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
|
1559
|
+
})
|
1560
|
+
|
1561
|
+
machine = StateMachine::Machine.new(@model)
|
1562
|
+
machine.event :park
|
1563
|
+
record = @model.new
|
1564
|
+
|
1565
|
+
machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
|
1566
|
+
assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
|
1567
|
+
end
|
1568
|
+
|
1569
|
+
def test_should_allow_customized_event_key_scoped_to_machine
|
1570
|
+
I18n.backend.store_translations(:en, {
|
1571
|
+
:activerecord => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
|
1572
|
+
})
|
1573
|
+
|
1574
|
+
machine = StateMachine::Machine.new(@model)
|
1575
|
+
machine.event :park
|
1576
|
+
record = @model.new
|
1577
|
+
|
1578
|
+
machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
|
1579
|
+
assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
|
1580
|
+
end
|
1581
|
+
|
1582
|
+
def test_should_allow_customized_event_key_unscoped
|
1583
|
+
I18n.backend.store_translations(:en, {
|
1584
|
+
:activerecord => {:state_machines => {:events => {:park => 'stop'}}}
|
1585
|
+
})
|
1586
|
+
|
1587
|
+
machine = StateMachine::Machine.new(@model)
|
1588
|
+
machine.event :park
|
1589
|
+
record = @model.new
|
1590
|
+
|
1591
|
+
machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
|
1592
|
+
assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
|
1593
|
+
end
|
1594
|
+
|
1269
1595
|
def test_should_only_add_locale_once_in_load_path
|
1270
1596
|
assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
|
1271
1597
|
|