state_machine 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +23 -7
- data/Appraisals +31 -25
- data/CHANGELOG.md +12 -0
- data/README.md +3 -1
- data/gemfiles/active_model-3.0.0.gemfile.lock +7 -5
- data/gemfiles/active_model-3.0.5.gemfile.lock +7 -5
- data/gemfiles/active_model-3.1.1.gemfile.lock +6 -4
- data/gemfiles/active_record-2.0.0.gemfile +1 -1
- data/gemfiles/active_record-2.0.0.gemfile.lock +7 -5
- data/gemfiles/active_record-2.0.5.gemfile +1 -1
- data/gemfiles/active_record-2.0.5.gemfile.lock +7 -5
- data/gemfiles/active_record-2.1.0.gemfile +1 -1
- data/gemfiles/active_record-2.1.0.gemfile.lock +7 -5
- data/gemfiles/active_record-2.1.2.gemfile +1 -1
- data/gemfiles/active_record-2.1.2.gemfile.lock +7 -5
- data/gemfiles/active_record-2.2.3.gemfile +1 -1
- data/gemfiles/active_record-2.2.3.gemfile.lock +7 -5
- data/gemfiles/active_record-2.3.12.gemfile +1 -1
- data/gemfiles/active_record-2.3.12.gemfile.lock +7 -5
- data/gemfiles/active_record-3.0.0.gemfile +1 -1
- data/gemfiles/active_record-3.0.0.gemfile.lock +8 -6
- data/gemfiles/active_record-3.0.5.gemfile +1 -1
- data/gemfiles/active_record-3.0.5.gemfile.lock +8 -6
- data/gemfiles/active_record-3.1.1.gemfile +1 -1
- data/gemfiles/active_record-3.1.1.gemfile.lock +7 -5
- data/gemfiles/data_mapper-0.10.2.gemfile +3 -3
- data/gemfiles/data_mapper-0.10.2.gemfile.lock +14 -5
- data/gemfiles/data_mapper-0.9.11.gemfile +3 -3
- data/gemfiles/data_mapper-0.9.11.gemfile.lock +7 -5
- data/gemfiles/data_mapper-0.9.4.gemfile +3 -3
- data/gemfiles/data_mapper-0.9.4.gemfile.lock +13 -13
- data/gemfiles/data_mapper-0.9.7.gemfile +3 -3
- data/gemfiles/data_mapper-0.9.7.gemfile.lock +13 -13
- data/gemfiles/data_mapper-1.0.0.gemfile +3 -3
- data/gemfiles/data_mapper-1.0.0.gemfile.lock +17 -8
- data/gemfiles/data_mapper-1.0.1.gemfile +3 -3
- data/gemfiles/data_mapper-1.0.1.gemfile.lock +17 -8
- data/gemfiles/data_mapper-1.0.2.gemfile +3 -3
- data/gemfiles/data_mapper-1.0.2.gemfile.lock +17 -8
- data/gemfiles/data_mapper-1.1.0.gemfile +3 -3
- data/gemfiles/data_mapper-1.1.0.gemfile.lock +17 -8
- data/gemfiles/data_mapper-1.2.0.gemfile +3 -3
- data/gemfiles/data_mapper-1.2.0.gemfile.lock +13 -4
- data/gemfiles/default.gemfile.lock +7 -5
- data/gemfiles/graphviz-0.9.0.gemfile.lock +6 -4
- data/gemfiles/graphviz-0.9.21.gemfile.lock +6 -4
- data/gemfiles/graphviz-1.0.0.gemfile.lock +6 -4
- data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +6 -3
- data/gemfiles/mongo_mapper-0.5.5.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.5.8.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.6.0.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.6.10.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.7.0.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.7.5.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.8.0.gemfile +2 -2
- data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.8.3.gemfile +2 -2
- data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.8.4.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +11 -8
- data/gemfiles/mongo_mapper-0.8.6.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +11 -8
- data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +14 -11
- data/gemfiles/mongoid-2.0.0.gemfile.lock +22 -17
- data/gemfiles/mongoid-2.1.4.gemfile.lock +21 -16
- data/gemfiles/mongoid-2.2.4.gemfile.lock +11 -8
- data/gemfiles/mongoid-2.3.3.gemfile.lock +11 -8
- data/gemfiles/sequel-2.11.0.gemfile.lock +7 -5
- data/gemfiles/sequel-2.12.0.gemfile.lock +7 -5
- data/gemfiles/sequel-2.8.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.0.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.13.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.14.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.23.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.24.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.29.0.gemfile.lock +5 -3
- data/lib/state_machine.rb +7 -1
- data/lib/state_machine/eval_helpers.rb +8 -9
- data/lib/state_machine/event.rb +10 -2
- data/lib/state_machine/integrations.rb +0 -1
- data/lib/state_machine/integrations/active_model.rb +46 -35
- data/lib/state_machine/integrations/active_record.rb +8 -0
- data/lib/state_machine/integrations/active_record/versions.rb +0 -20
- data/lib/state_machine/integrations/data_mapper.rb +22 -21
- data/lib/state_machine/integrations/data_mapper/versions.rb +0 -27
- data/lib/state_machine/integrations/mongo_mapper.rb +8 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +0 -4
- data/lib/state_machine/integrations/mongoid.rb +15 -2
- data/lib/state_machine/integrations/mongoid/versions.rb +0 -7
- data/lib/state_machine/integrations/sequel.rb +14 -0
- data/lib/state_machine/machine.rb +12 -0
- data/lib/state_machine/state.rb +4 -3
- data/lib/state_machine/state_context.rb +10 -9
- data/lib/state_machine/transition.rb +6 -1
- data/lib/state_machine/version.rb +1 -1
- data/state_machine.gemspec +1 -1
- data/test/functional/state_machine_test.rb +21 -1
- data/test/unit/event_test.rb +10 -0
- data/test/unit/integrations/active_model_test.rb +31 -29
- data/test/unit/integrations/active_record_test.rb +56 -26
- data/test/unit/integrations/data_mapper_test.rb +34 -57
- data/test/unit/integrations/mongo_mapper_test.rb +30 -24
- data/test/unit/integrations/mongoid_test.rb +114 -29
- data/test/unit/integrations/sequel_test.rb +36 -0
- data/test/unit/invalid_transition_test.rb +38 -0
- data/test/unit/machine_test.rb +38 -0
- data/test/unit/state_context_test.rb +29 -9
- data/test/unit/state_test.rb +21 -1
- data/test/unit/transition_collection_test.rb +72 -26
- data/test/unit/transition_test.rb +84 -73
- metadata +8 -8
|
@@ -9,10 +9,6 @@ module StateMachine
|
|
|
9
9
|
def action_hook
|
|
10
10
|
action
|
|
11
11
|
end
|
|
12
|
-
|
|
13
|
-
def mark_dirty(object, value)
|
|
14
|
-
object.original_values[self.attribute] = "#{value}-ignored" if object.original_values[self.attribute] == value
|
|
15
|
-
end
|
|
16
12
|
end
|
|
17
13
|
|
|
18
14
|
version '0.9.x - 0.10.x' do
|
|
@@ -37,29 +33,6 @@ module StateMachine
|
|
|
37
33
|
end
|
|
38
34
|
end
|
|
39
35
|
|
|
40
|
-
version '0.10.x' do
|
|
41
|
-
def self.active?
|
|
42
|
-
::DataMapper::VERSION =~ /^0\.10\./
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def mark_dirty(object, value)
|
|
46
|
-
property = owner_class.properties[self.attribute]
|
|
47
|
-
object.original_attributes[property] = "#{value}-ignored" unless object.original_attributes.include?(property)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
version '1.0.x - 1.1.x' do
|
|
52
|
-
def self.active?
|
|
53
|
-
::DataMapper::VERSION =~ /^1\.[01]\./
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def mark_dirty(object, value)
|
|
57
|
-
object.persisted_state = ::DataMapper::Resource::State::Dirty.new(object) if object.persisted_state.is_a?(::DataMapper::Resource::State::Clean)
|
|
58
|
-
property = owner_class.properties[self.attribute]
|
|
59
|
-
object.persisted_state.original_attributes[property] = value unless object.persisted_state.original_attributes.include?(property)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
36
|
version '1.0.0' do
|
|
64
37
|
def self.active?
|
|
65
38
|
::DataMapper::VERSION == '1.0.0'
|
|
@@ -158,6 +158,14 @@ module StateMachine
|
|
|
158
158
|
# *not* because a matching transition was not available, no error messages
|
|
159
159
|
# will be added to the state attribute.
|
|
160
160
|
#
|
|
161
|
+
# In addition, if you're using the <tt>ignite!</tt> version of the event,
|
|
162
|
+
# then the failure reason (such as the current validation errors) will be
|
|
163
|
+
# included in the exception that gets raised when the event fails. For
|
|
164
|
+
# example, assuming there's a validation on a field called +name+ on the class:
|
|
165
|
+
#
|
|
166
|
+
# vehicle = Vehicle.new
|
|
167
|
+
# vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
|
|
168
|
+
#
|
|
161
169
|
# == Scopes
|
|
162
170
|
#
|
|
163
171
|
# To assist in filtering models with specific states, a series of basic
|
|
@@ -156,6 +156,14 @@ module StateMachine
|
|
|
156
156
|
# *not* because a matching transition was not available, no error messages
|
|
157
157
|
# will be added to the state attribute.
|
|
158
158
|
#
|
|
159
|
+
# In addition, if you're using the <tt>ignite!</tt> version of the event,
|
|
160
|
+
# then the failure reason (such as the current validation errors) will be
|
|
161
|
+
# included in the exception that gets raised when the event fails. For
|
|
162
|
+
# example, assuming there's a validation on a field called +name+ on the class:
|
|
163
|
+
#
|
|
164
|
+
# vehicle = Vehicle.new
|
|
165
|
+
# vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
|
|
166
|
+
#
|
|
159
167
|
# == Scopes
|
|
160
168
|
#
|
|
161
169
|
# To assist in filtering models with specific states, a series of basic
|
|
@@ -363,8 +371,13 @@ module StateMachine
|
|
|
363
371
|
def define_state_initializer
|
|
364
372
|
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
|
365
373
|
def initialize(*)
|
|
366
|
-
@attributes
|
|
367
|
-
self.class.state_machines.initialize_states(self
|
|
374
|
+
@attributes ||= {}
|
|
375
|
+
self.class.state_machines.initialize_states(self, :dynamic => false)
|
|
376
|
+
|
|
377
|
+
super do |*args|
|
|
378
|
+
self.class.state_machines.initialize_states(self, :static => false)
|
|
379
|
+
yield(*args) if block_given?
|
|
380
|
+
end
|
|
368
381
|
end
|
|
369
382
|
end_eval
|
|
370
383
|
end
|
|
@@ -49,13 +49,6 @@ module StateMachine
|
|
|
49
49
|
|
|
50
50
|
result
|
|
51
51
|
end
|
|
52
|
-
|
|
53
|
-
protected
|
|
54
|
-
# Mongoid uses its own implementation of dirty tracking instead of
|
|
55
|
-
# ActiveModel's and doesn't support the #{attribute}_will_change! APIs
|
|
56
|
-
def supports_dirty_tracking?(object)
|
|
57
|
-
false
|
|
58
|
-
end
|
|
59
52
|
end
|
|
60
53
|
end
|
|
61
54
|
end
|
|
@@ -163,6 +163,14 @@ module StateMachine
|
|
|
163
163
|
# *not* because a matching transition was not available, no error messages
|
|
164
164
|
# will be added to the state attribute.
|
|
165
165
|
#
|
|
166
|
+
# In addition, if you're using the <tt>ignite!</tt> version of the event,
|
|
167
|
+
# then the failure reason (such as the current validation errors) will be
|
|
168
|
+
# included in the exception that gets raised when the event fails. For
|
|
169
|
+
# example, assuming there's a validation on a field called +name+ on the class:
|
|
170
|
+
#
|
|
171
|
+
# vehicle = Vehicle.new
|
|
172
|
+
# vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
|
|
173
|
+
#
|
|
166
174
|
# == Scopes
|
|
167
175
|
#
|
|
168
176
|
# To assist in filtering models with specific states, a series of class
|
|
@@ -297,6 +305,12 @@ module StateMachine
|
|
|
297
305
|
object.errors.add(self.attribute(attribute), generate_message(message, values))
|
|
298
306
|
end
|
|
299
307
|
|
|
308
|
+
# Describes the current validation errors on the given object. If none
|
|
309
|
+
# are specific, then the default error is interpeted as a "halt".
|
|
310
|
+
def errors_for(object)
|
|
311
|
+
object.errors.empty? ? 'Transition halted' : object.errors.full_messages * ', '
|
|
312
|
+
end
|
|
313
|
+
|
|
300
314
|
# Resets any errors previously added when invalidating the given object
|
|
301
315
|
def reset(object)
|
|
302
316
|
object.errors.clear
|
|
@@ -1814,6 +1814,13 @@ module StateMachine
|
|
|
1814
1814
|
def invalidate(object, attribute, message, values = [])
|
|
1815
1815
|
end
|
|
1816
1816
|
|
|
1817
|
+
# Gets a description of the errors for the given object. This is used to
|
|
1818
|
+
# provide more detailed information when an InvalidTransition exception is
|
|
1819
|
+
# raised.
|
|
1820
|
+
def errors_for(object)
|
|
1821
|
+
''
|
|
1822
|
+
end
|
|
1823
|
+
|
|
1817
1824
|
# Resets any errors previously added when invalidating the given object.
|
|
1818
1825
|
#
|
|
1819
1826
|
# By default, this is a no-op.
|
|
@@ -1993,6 +2000,11 @@ module StateMachine
|
|
|
1993
2000
|
machine.events.transitions_for(object, *args)
|
|
1994
2001
|
end
|
|
1995
2002
|
|
|
2003
|
+
# Fire an arbitrary event for this machine
|
|
2004
|
+
define_helper(:instance, "fire_#{attribute(:event)}") do |machine, object, event, *args|
|
|
2005
|
+
machine.events.fetch(event).fire(object, *args)
|
|
2006
|
+
end
|
|
2007
|
+
|
|
1996
2008
|
# Add helpers for tracking the event / transition to invoke when the
|
|
1997
2009
|
# action is called
|
|
1998
2010
|
if action
|
data/lib/state_machine/state.rb
CHANGED
|
@@ -195,7 +195,7 @@ module StateMachine
|
|
|
195
195
|
# Calls the method defined by the current state of the machine
|
|
196
196
|
context.class_eval <<-end_eval, __FILE__, __LINE__ + 1
|
|
197
197
|
def #{method}(*args, &block)
|
|
198
|
-
self.class.state_machine(#{machine_name.inspect}).states.
|
|
198
|
+
self.class.state_machine(#{machine_name.inspect}).states.fetch(#{name.inspect}).call(self, #{method.inspect}, lambda {super(*args, &block)}, *args, &block)
|
|
199
199
|
end
|
|
200
200
|
end_eval
|
|
201
201
|
end
|
|
@@ -213,11 +213,12 @@ module StateMachine
|
|
|
213
213
|
# If the method has never been defined for this state, then a NoMethodError
|
|
214
214
|
# will be raised.
|
|
215
215
|
def call(object, method, method_missing = nil, *args, &block)
|
|
216
|
-
if context_method = methods[method.to_sym]
|
|
216
|
+
if machine.states.matches?(object, name) && context_method = methods[method.to_sym]
|
|
217
217
|
# Method is defined by the state: proxy it through
|
|
218
218
|
context_method.bind(object).call(*args, &block)
|
|
219
219
|
else
|
|
220
|
-
# Dispatch to the superclass since
|
|
220
|
+
# Dispatch to the superclass since the object either isn't in this state
|
|
221
|
+
# or this state doesn't handle the method
|
|
221
222
|
method_missing.call if method_missing
|
|
222
223
|
end
|
|
223
224
|
end
|
|
@@ -79,20 +79,21 @@ module StateMachine
|
|
|
79
79
|
# *not* need to specify the <tt>:from</tt> option for the transition. For
|
|
80
80
|
# example:
|
|
81
81
|
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
82
|
+
# state_machine do
|
|
83
|
+
# state :parked do
|
|
84
|
+
# transition :to => :idling, :on => [:ignite, :shift_up] # Transitions to :idling
|
|
85
|
+
# transition :from => [:idling, :parked], :on => :park, :unless => :seatbelt_on? # Transitions to :parked if seatbelt is off
|
|
86
|
+
# end
|
|
87
|
+
# end
|
|
88
88
|
#
|
|
89
89
|
# See StateMachine::Machine#transition for a description of the possible
|
|
90
90
|
# configurations for defining transitions.
|
|
91
91
|
def transition(options)
|
|
92
|
-
assert_valid_keys(options, :to, :on, :if, :unless)
|
|
93
|
-
raise ArgumentError, 'Must specify :
|
|
92
|
+
assert_valid_keys(options, :from, :to, :on, :if, :unless)
|
|
93
|
+
raise ArgumentError, 'Must specify :on event' unless options[:on]
|
|
94
|
+
raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from]
|
|
94
95
|
|
|
95
|
-
machine.transition(options.merge(:from => state.name))
|
|
96
|
+
machine.transition(options.merge(options[:to] ? {:from => state.name} : {:to => state.name}))
|
|
96
97
|
end
|
|
97
98
|
|
|
98
99
|
# Hooks in condition-merging to methods that don't exist in this module
|
|
@@ -15,8 +15,11 @@ module StateMachine
|
|
|
15
15
|
@from_state = machine.states.match!(object)
|
|
16
16
|
@from = machine.read(object, :state)
|
|
17
17
|
@event = machine.events.fetch(event)
|
|
18
|
+
errors = machine.errors_for(object)
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
message = "Cannot transition #{machine.name} via :#{self.event} from #{from_name.inspect}"
|
|
21
|
+
message << " (Reason(s): #{errors})" unless errors.empty?
|
|
22
|
+
super(object, message)
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
# The event that triggered the failed transition
|
|
@@ -351,6 +354,8 @@ module StateMachine
|
|
|
351
354
|
# around callbacks when the remainder of the callback will be executed at
|
|
352
355
|
# a later point in time.
|
|
353
356
|
def pause
|
|
357
|
+
raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.' if RUBY_PLATFORM == 'java'
|
|
358
|
+
|
|
354
359
|
unless @resume_block
|
|
355
360
|
require 'continuation' unless defined?(callcc)
|
|
356
361
|
callcc do |block|
|
data/state_machine.gemspec
CHANGED
|
@@ -40,7 +40,7 @@ class ModelBase
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
class Vehicle < ModelBase
|
|
43
|
-
attr_accessor :auto_shop, :seatbelt_on, :insurance_premium, :force_idle, :callbacks, :saved, :time_elapsed
|
|
43
|
+
attr_accessor :auto_shop, :seatbelt_on, :insurance_premium, :force_idle, :callbacks, :saved, :time_elapsed, :last_transition_args
|
|
44
44
|
|
|
45
45
|
def initialize(attributes = {})
|
|
46
46
|
attributes = {
|
|
@@ -58,6 +58,7 @@ class Vehicle < ModelBase
|
|
|
58
58
|
|
|
59
59
|
# Defines the state machine for the state of the vehicled
|
|
60
60
|
state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked}, :action => :save do
|
|
61
|
+
before_transition {|vehicle, transition| vehicle.last_transition_args = transition.args}
|
|
61
62
|
before_transition :parked => any, :do => :put_on_seatbelt
|
|
62
63
|
before_transition any => :stalled, :do => :increase_insurance_premium
|
|
63
64
|
after_transition any => :parked, :do => lambda {|vehicle| vehicle.seatbelt_on = false}
|
|
@@ -340,6 +341,25 @@ class VehicleUnsavedTest < Test::Unit::TestCase
|
|
|
340
341
|
]], @vehicle.state_paths(:to => :first_gear)
|
|
341
342
|
end
|
|
342
343
|
|
|
344
|
+
def test_should_allow_generic_event_to_fire
|
|
345
|
+
assert @vehicle.fire_state_event(:ignite)
|
|
346
|
+
assert_equal 'idling', @vehicle.state
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def test_should_pass_arguments_through_to_generic_event_runner
|
|
350
|
+
@vehicle.fire_state_event(:ignite, 1, 2, 3)
|
|
351
|
+
assert_equal [1, 2, 3], @vehicle.last_transition_args
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def test_should_allow_skipping_action_through_generic_event_runner
|
|
355
|
+
@vehicle.fire_state_event(:ignite, false)
|
|
356
|
+
assert_equal false, @vehicle.saved
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def test_should_raise_error_with_invalid_event_through_generic_event_runer
|
|
360
|
+
assert_raise(IndexError) { @vehicle.fire_state_event(:invalid) }
|
|
361
|
+
end
|
|
362
|
+
|
|
343
363
|
def test_should_allow_ignite
|
|
344
364
|
assert @vehicle.ignite
|
|
345
365
|
assert_equal 'idling', @vehicle.state
|
data/test/unit/event_test.rb
CHANGED
|
@@ -474,6 +474,11 @@ class EventWithTransitionsTest < Test::Unit::TestCase
|
|
|
474
474
|
assert_equal [:parked, :idling, :first_gear, :stalled], @event.known_states
|
|
475
475
|
end
|
|
476
476
|
|
|
477
|
+
def test_should_clear_known_states_on_reset
|
|
478
|
+
@event.reset
|
|
479
|
+
assert_equal [], @event.known_states
|
|
480
|
+
end
|
|
481
|
+
|
|
477
482
|
def test_should_use_pretty_inspect
|
|
478
483
|
assert_match "#<StateMachine::Event name=:ignite transitions=[:parked => :idling, :first_gear => :idling]>", @event.inspect
|
|
479
484
|
end
|
|
@@ -690,6 +695,11 @@ class EventWithMatchingEnabledTransitionsTest < Test::Unit::TestCase
|
|
|
690
695
|
assert_equal [], @object.errors
|
|
691
696
|
end
|
|
692
697
|
|
|
698
|
+
def test_should_not_be_able_to_fire_on_reset
|
|
699
|
+
@event.reset
|
|
700
|
+
assert !@event.can_fire?(@object)
|
|
701
|
+
end
|
|
702
|
+
|
|
693
703
|
def teardown
|
|
694
704
|
StateMachine::Integrations.send(:remove_const, 'Custom')
|
|
695
705
|
end
|
|
@@ -17,7 +17,7 @@ module ActiveModelTest
|
|
|
17
17
|
def self.model_attribute(name)
|
|
18
18
|
define_method(name) { instance_variable_get("@#{name}") }
|
|
19
19
|
define_method("#{name}=") do |value|
|
|
20
|
-
send("#{name}_will_change!") if self.class <= ActiveModel::Dirty &&
|
|
20
|
+
send("#{name}_will_change!") if self.class <= ActiveModel::Dirty && value != instance_variable_get("@#{name}")
|
|
21
21
|
instance_variable_set("@#{name}", value)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -79,10 +79,6 @@ module ActiveModelTest
|
|
|
79
79
|
assert StateMachine::Integrations::ActiveModel.available?
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
def test_should_match_if_class_includes_dirty_feature
|
|
83
|
-
assert StateMachine::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Dirty })
|
|
84
|
-
end
|
|
85
|
-
|
|
86
82
|
def test_should_match_if_class_includes_observing_feature
|
|
87
83
|
assert StateMachine::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Observing })
|
|
88
84
|
end
|
|
@@ -350,12 +346,12 @@ module ActiveModelTest
|
|
|
350
346
|
@transition.perform
|
|
351
347
|
end
|
|
352
348
|
|
|
353
|
-
def
|
|
354
|
-
assert_equal
|
|
349
|
+
def test_should_not_include_state_in_changed_attributes
|
|
350
|
+
assert_equal [], @record.changed
|
|
355
351
|
end
|
|
356
352
|
|
|
357
|
-
def
|
|
358
|
-
assert_equal
|
|
353
|
+
def test_should_not_track_attribute_changes
|
|
354
|
+
assert_equal nil, @record.changes['state']
|
|
359
355
|
end
|
|
360
356
|
end
|
|
361
357
|
|
|
@@ -408,12 +404,12 @@ module ActiveModelTest
|
|
|
408
404
|
@transition.perform
|
|
409
405
|
end
|
|
410
406
|
|
|
411
|
-
def
|
|
412
|
-
assert_equal
|
|
407
|
+
def test_should_not_include_state_in_changed_attributes
|
|
408
|
+
assert_equal [], @record.changed
|
|
413
409
|
end
|
|
414
410
|
|
|
415
|
-
def
|
|
416
|
-
assert_equal
|
|
411
|
+
def test_should_not_track_attribute_changes
|
|
412
|
+
assert_equal nil, @record.changes['status']
|
|
417
413
|
end
|
|
418
414
|
end
|
|
419
415
|
|
|
@@ -430,24 +426,12 @@ module ActiveModelTest
|
|
|
430
426
|
@record.state_event = 'ignite'
|
|
431
427
|
end
|
|
432
428
|
|
|
433
|
-
def
|
|
434
|
-
assert_equal
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
def test_should_track_attribute_change
|
|
438
|
-
assert_equal %w(parked parked), @record.changes['state']
|
|
439
|
-
end
|
|
440
|
-
|
|
441
|
-
def test_should_not_reset_changes_on_multiple_changes
|
|
442
|
-
@record.state_event = 'ignite'
|
|
443
|
-
assert_equal %w(parked parked), @record.changes['state']
|
|
429
|
+
def test_should_not_include_state_in_changed_attributes
|
|
430
|
+
assert_equal [], @record.changed
|
|
444
431
|
end
|
|
445
432
|
|
|
446
|
-
def
|
|
447
|
-
|
|
448
|
-
@record.state_event = nil
|
|
449
|
-
|
|
450
|
-
assert_equal [], @record.changed
|
|
433
|
+
def test_should_not_track_attribute_change
|
|
434
|
+
assert_equal nil, @record.changes['state']
|
|
451
435
|
end
|
|
452
436
|
end
|
|
453
437
|
|
|
@@ -694,6 +678,24 @@ module ActiveModelTest
|
|
|
694
678
|
assert @record.valid?
|
|
695
679
|
end
|
|
696
680
|
end
|
|
681
|
+
|
|
682
|
+
class MachineErrorsTest < BaseTestCase
|
|
683
|
+
def setup
|
|
684
|
+
@model = new_model { include ActiveModel::Validations }
|
|
685
|
+
@machine = StateMachine::Machine.new(@model)
|
|
686
|
+
@record = @model.new
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def test_should_be_able_to_describe_current_errors
|
|
690
|
+
@record.errors.add(:id, 'cannot be blank')
|
|
691
|
+
@record.errors.add(:state, 'is invalid')
|
|
692
|
+
assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def test_should_describe_as_halted_with_no_errors
|
|
696
|
+
assert_equal 'Transition halted', @machine.errors_for(@record)
|
|
697
|
+
end
|
|
698
|
+
end
|
|
697
699
|
|
|
698
700
|
class MachineWithStateDrivenValidationsTest < BaseTestCase
|
|
699
701
|
def setup
|
|
@@ -189,6 +189,15 @@ module ActiveRecordTest
|
|
|
189
189
|
assert_equal [record], block_args
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
+
def test_should_set_attributes_prior_to_initialize_block
|
|
193
|
+
state = nil
|
|
194
|
+
record = @model.new do |record|
|
|
195
|
+
state = record.state
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
assert_equal 'parked', state
|
|
199
|
+
end
|
|
200
|
+
|
|
192
201
|
def test_should_set_attributes_prior_to_after_initialize_hook
|
|
193
202
|
state = nil
|
|
194
203
|
@model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
|
|
@@ -264,6 +273,15 @@ module ActiveRecordTest
|
|
|
264
273
|
assert_equal [record], block_args
|
|
265
274
|
end
|
|
266
275
|
|
|
276
|
+
def test_should_set_attributes_prior_to_initialize_block
|
|
277
|
+
state = nil
|
|
278
|
+
record = @model.new do |record|
|
|
279
|
+
state = record.state
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
assert_equal 'parked', state
|
|
283
|
+
end
|
|
284
|
+
|
|
267
285
|
def test_should_set_attributes_prior_to_after_initialize_hook
|
|
268
286
|
state = nil
|
|
269
287
|
@model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
|
|
@@ -605,8 +623,14 @@ module ActiveRecordTest
|
|
|
605
623
|
@transition.perform
|
|
606
624
|
end
|
|
607
625
|
|
|
608
|
-
|
|
609
|
-
|
|
626
|
+
if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty)
|
|
627
|
+
def test_should_not_update_record
|
|
628
|
+
assert_equal @timestamp, @record.updated_at
|
|
629
|
+
end
|
|
630
|
+
else
|
|
631
|
+
def test_should_update_record
|
|
632
|
+
assert_not_equal @timestamp, @record.updated_at
|
|
633
|
+
end
|
|
610
634
|
end
|
|
611
635
|
end
|
|
612
636
|
|
|
@@ -657,12 +681,12 @@ module ActiveRecordTest
|
|
|
657
681
|
@transition.perform(false)
|
|
658
682
|
end
|
|
659
683
|
|
|
660
|
-
def
|
|
661
|
-
assert_equal
|
|
684
|
+
def test_should_not_include_state_in_changed_attributes
|
|
685
|
+
assert_equal [], @record.changed
|
|
662
686
|
end
|
|
663
687
|
|
|
664
|
-
def
|
|
665
|
-
assert_equal
|
|
688
|
+
def test_should_not_track_attribute_changes
|
|
689
|
+
assert_equal nil, @record.changes['state']
|
|
666
690
|
end
|
|
667
691
|
end
|
|
668
692
|
|
|
@@ -711,12 +735,12 @@ module ActiveRecordTest
|
|
|
711
735
|
@transition.perform(false)
|
|
712
736
|
end
|
|
713
737
|
|
|
714
|
-
def
|
|
715
|
-
assert_equal
|
|
738
|
+
def test_should_not_include_state_in_changed_attributes
|
|
739
|
+
assert_equal [], @record.changed
|
|
716
740
|
end
|
|
717
741
|
|
|
718
|
-
def
|
|
719
|
-
assert_equal
|
|
742
|
+
def test_should_not_track_attribute_changes
|
|
743
|
+
assert_equal nil, @record.changes['status']
|
|
720
744
|
end
|
|
721
745
|
end
|
|
722
746
|
|
|
@@ -730,24 +754,12 @@ module ActiveRecordTest
|
|
|
730
754
|
@record.state_event = 'ignite'
|
|
731
755
|
end
|
|
732
756
|
|
|
733
|
-
def
|
|
734
|
-
assert_equal
|
|
735
|
-
end
|
|
736
|
-
|
|
737
|
-
def test_should_track_attribute_change
|
|
738
|
-
assert_equal %w(parked parked), @record.changes['state']
|
|
739
|
-
end
|
|
740
|
-
|
|
741
|
-
def test_should_not_reset_changes_on_multiple_changes
|
|
742
|
-
@record.state_event = 'ignite'
|
|
743
|
-
assert_equal %w(parked parked), @record.changes['state']
|
|
757
|
+
def test_should_not_include_state_in_changed_attributes
|
|
758
|
+
assert_equal [], @record.changed
|
|
744
759
|
end
|
|
745
760
|
|
|
746
|
-
def
|
|
747
|
-
|
|
748
|
-
@record.state_event = nil
|
|
749
|
-
|
|
750
|
-
assert_equal [], @record.changed
|
|
761
|
+
def test_should_not_track_attribute_change
|
|
762
|
+
assert_equal nil, @record.changes['state']
|
|
751
763
|
end
|
|
752
764
|
end
|
|
753
765
|
else
|
|
@@ -1111,6 +1123,24 @@ module ActiveRecordTest
|
|
|
1111
1123
|
assert @record.valid?
|
|
1112
1124
|
end
|
|
1113
1125
|
end
|
|
1126
|
+
|
|
1127
|
+
class MachineErrorsTest < BaseTestCase
|
|
1128
|
+
def setup
|
|
1129
|
+
@model = new_model
|
|
1130
|
+
@machine = StateMachine::Machine.new(@model)
|
|
1131
|
+
@record = @model.new
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
def test_should_be_able_to_describe_current_errors
|
|
1135
|
+
@record.errors.add(:id, 'cannot be blank')
|
|
1136
|
+
@record.errors.add(:state, 'is invalid')
|
|
1137
|
+
assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort
|
|
1138
|
+
end
|
|
1139
|
+
|
|
1140
|
+
def test_should_describe_as_halted_with_no_errors
|
|
1141
|
+
assert_equal 'Transition halted', @machine.errors_for(@record)
|
|
1142
|
+
end
|
|
1143
|
+
end
|
|
1114
1144
|
|
|
1115
1145
|
class MachineWithStateDrivenValidationsTest < BaseTestCase
|
|
1116
1146
|
def setup
|