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.
@@ -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) do
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
- # Revert object modifications
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 unless transition was successful
138
- transition.machine.write(object, :event, transition.event) unless complete && result
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 completed successfully
141
- transition.machine.write(object, :event_transition, !complete && result ? transition : nil)
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
 
@@ -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
- # Raise exception as if the method never existed on the original object
194
- raise NoMethodError, "undefined method '#{method}' for #{object} with #{name || 'nil'} #{machine.name}"
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__) + '/lib')
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
@@ -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
@@ -803,7 +803,7 @@ begin
803
803
  end
804
804
 
805
805
  def test_should_use_event_name_as_label
806
- assert_equal 'park', @edges.first['label']
806
+ assert_equal 'park', @edges.first['label'].to_s.gsub('"', '')
807
807
  end
808
808
  end
809
809
 
@@ -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.1.0'
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
- require 'active_record/test_case'
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
- # Add model/observer creation helpers
22
- ActiveRecord::TestCase.class_eval do
23
- # Creates a new ActiveRecord model (and the associated table)
24
- def new_model(create_table = true, &block)
25
- model = Class.new(ActiveRecord::Base) do
26
- connection.create_table(:foo, :force => true) {|t| t.string(:state)} if create_table
27
- set_table_name('foo')
28
-
29
- def self.name; 'ActiveRecordTest::Foo'; end
30
- end
31
- model.class_eval(&block) if block_given?
32
- model
33
- end
34
-
35
- # Creates a new ActiveRecord observer
36
- def new_observer(model, &block)
37
- observer = Class.new(ActiveRecord::Observer) do
38
- attr_accessor :notifications
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
- def initialize
41
- super
42
- @notifications = []
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
- end
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 test_should_not_override_the_column_writer
197
- record = @model.new
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 < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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.connection.table_exists?(:foo)
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 MachineWithStaticInitialStateTest < ActiveRecord::TestCase
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 do
261
- define_method(:after_initialize) do
262
- state = self.state
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 < ActiveRecord::TestCase
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 do
323
- define_method(:after_initialize) do
324
- state = self.state
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 MachineWithConflictingPredicateTest < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 MachineWithComplexPluralizationTest < ActiveRecord::TestCase
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, :status)
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 test_should_create_singular_with_scope
472
- assert @model.respond_to?(:with_status)
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 test_should_create_plural_with_scope
476
- assert @model.respond_to?(:with_statuses)
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 MachineWithOwnerSubclassTest < ActiveRecord::TestCase
455
+ class MachineWithLoopbackTest < BaseTestCase
481
456
  def setup
482
- @model = new_model
483
- @machine = StateMachine::Machine.new(@model, :state)
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
- @subclass = Class.new(@model)
486
- @subclass_machine = @subclass.state_machine(:state) {}
487
- @subclass_machine.state :parked, :idling, :first_gear
522
+ def test_should_track_attribute_changes
523
+ assert_equal %w(parked parked), @record.changes['state']
524
+ end
488
525
  end
489
526
 
490
- def test_should_only_include_records_with_subclass_states_in_with_scope
491
- parked = @subclass.create :state => 'parked'
492
- idling = @subclass.create :state => 'idling'
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
- assert_equal [parked, idling], @subclass.with_states(:parked, :idling)
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
- def test_should_only_include_records_without_subclass_states_in_without_scope
498
- parked = @subclass.create :state => 'parked'
499
- idling = @subclass.create :state => 'idling'
500
- first_gear = @subclass.create :state => 'first_gear'
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
- assert_equal [parked, idling], @subclass.without_states(:first_gear)
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 MachineWithCustomAttributeTest < ActiveRecord::TestCase
582
+ class MachineWithoutTransactionsTest < BaseTestCase
507
583
  def setup
508
- @model = new_model do
509
- alias_attribute :vehicle_status, :state
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
- @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
513
- @machine.state :parked
514
-
515
- @record = @model.new
594
+ assert_equal 1, @model.count
516
595
  end
517
596
 
518
- def test_should_add_validation_errors_to_custom_attribute
519
- @record.vehicle_status = 'invalid'
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
- assert !@record.valid?
522
- assert_equal ['Vehicle status is invalid'], @record.errors.full_messages
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
- @record.vehicle_status = 'parked'
525
- assert @record.valid?
619
+ assert_equal 0, @model.count
526
620
  end
527
621
 
528
- def test_should_check_custom_attribute_for_predicate
529
- @record.vehicle_status = nil
530
- assert !@record.status?(:parked)
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
- @record.vehicle_status = 'parked'
533
- assert @record.status?(:parked)
628
+ assert_equal 1, @model.count
534
629
  end
535
630
  end
536
631
 
537
- class MachineWithCallbacksTest < ActiveRecord::TestCase
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 MachineWithLoopbackTest < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 MachineWithValidationsTest < ActiveRecord::TestCase
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 MachineWithStateDrivenValidationsTest < ActiveRecord::TestCase
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 MachineWithFailedAfterCallbacksTest < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 < ActiveRecord::TestCase
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 test_should_invalidate_using_i18n_default
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 = StateMachine::Event.new(machine, :ignite)
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 test_should_invalidate_using_customized_i18n_key_if_specified
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 test_should_invalidate_using_customized_i18n_string_if_specified
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