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