state_machine 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|