state_machine 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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