Skip to content

Commit

Permalink
Fix observer callbacks being run incorrectly when using nil states in…
Browse files Browse the repository at this point in the history
… ActiveModel-based integrations

Add tests proving that translations work for nil states
  • Loading branch information
obrie committed Oct 21, 2011
1 parent fe2b9be commit 83729ee
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -1,5 +1,6 @@
# master

* Fix observer callbacks being run incorrectly when using nil states in ActiveModel-based integrations
* Remove ActiveModel Observer method chains in order to better ensure compatibility
* Update DataMapper integration for 1.2.0+ support [Markus Schirp]
* Provide access to the human state name in invalid_transition translations
Expand Down
4 changes: 2 additions & 2 deletions lib/state_machine/integrations/active_model.rb
Expand Up @@ -556,8 +556,8 @@ def add_events(new_events)
def notify(type, object, transition)
name = self.name
event = transition.qualified_event
from = transition.from_name
to = transition.to_name
from = transition.from_name || 'nil'
to = transition.to_name || 'nil'

# Machine-specific updates
["#{type}_#{event}", "#{type}_transition_#{name}"].each do |event_segment|
Expand Down
4 changes: 1 addition & 3 deletions lib/state_machine/state.rb
Expand Up @@ -179,9 +179,7 @@ def matches?(other_value)
# This can be called multiple times. Each time a new context is created,
# a new module will be included in the owner class.
def context(&block)
owner_class = machine.owner_class
machine_name = machine.name
name = self.name

# Evaluate the method definitions
context = StateContext.new(self)
Expand All @@ -199,7 +197,7 @@ def #{method}(*args, &block)

# Include the context so that it can be bound to the owner class (the
# context is considered an ancestor, so it's allowed to be bound)
owner_class.class_eval { include context }
machine.owner_class.class_eval { include context }

context
end
Expand Down
58 changes: 58 additions & 0 deletions test/unit/integrations/active_model_test.rb
Expand Up @@ -798,6 +798,54 @@ def before_ignite(*args)
@transition.perform
assert_equal [instance], instance.notifications
end

def test_should_support_nil_from_states
callbacks = [
:before_ignite_from_nil_to_idling,
:before_ignite_from_nil,
:before_transition_state_from_nil_to_idling,
:before_transition_state_from_nil
]

notified = false
observer = new_observer(@model) do
callbacks.each do |callback|
define_method(callback) do |*args|
notifications << callback
end
end
end

instance = observer.instance

transition = StateMachine::Transition.new(@record, @machine, :ignite, nil, :idling)
transition.perform
assert_equal callbacks, instance.notifications
end

def test_should_support_nil_to_states
callbacks = [
:before_ignite_from_parked_to_nil,
:before_ignite_to_nil,
:before_transition_state_from_parked_to_nil,
:before_transition_state_to_nil
]

notified = false
observer = new_observer(@model) do
callbacks.each do |callback|
define_method(callback) do |*args|
notifications << callback
end
end
end

instance = observer.instance

transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, nil)
transition.perform
assert_equal callbacks, instance.notifications
end
end

class MachineWithNamespacedObserversTest < BaseTestCase
Expand Down Expand Up @@ -1027,6 +1075,16 @@ def test_should_allow_customized_state_key_unscoped
assert_equal 'shutdown', machine.state(:parked).human_name
end

def test_should_support_nil_state_key
I18n.backend.store_translations(:en, {
:activemodel => {:state_machines => {:states => {:nil => 'empty'}}}
})

machine = StateMachine::Machine.new(@model)

assert_equal 'empty', machine.state(nil).human_name
end

def test_should_allow_customized_event_key_scoped_to_class_and_machine
I18n.backend.store_translations(:en, {
:activemodel => {:state_machines => {:'active_model_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
Expand Down
58 changes: 58 additions & 0 deletions test/unit/integrations/active_record_test.rb
Expand Up @@ -1592,6 +1592,54 @@ def before_ignite(*args)
@transition.perform
assert_equal [:before_ignite, [:before_save, @object]], instance.notifications
end

def test_should_support_nil_from_states
callbacks = [
:before_ignite_from_nil_to_idling,
:before_ignite_from_nil,
:before_transition_state_from_nil_to_idling,
:before_transition_state_from_nil
]

notified = false
observer = new_observer(@model) do
callbacks.each do |callback|
define_method(callback) do |*args|
notifications << callback
end
end
end

instance = observer.instance

transition = StateMachine::Transition.new(@record, @machine, :ignite, nil, :idling)
transition.perform
assert_equal callbacks, instance.notifications
end

def test_should_support_nil_to_states
callbacks = [
:before_ignite_from_parked_to_nil,
:before_ignite_to_nil,
:before_transition_state_from_parked_to_nil,
:before_transition_state_to_nil
]

notified = false
observer = new_observer(@model) do
callbacks.each do |callback|
define_method(callback) do |*args|
notifications << callback
end
end
end

instance = observer.instance

transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, nil)
transition.perform
assert_equal callbacks, instance.notifications
end
end

class MachineWithNamespacedObserversTest < BaseTestCase
Expand Down Expand Up @@ -1988,6 +2036,16 @@ def test_should_allow_customized_state_key_unscoped
assert_equal 'shutdown', machine.state(:parked).human_name
end

def test_should_support_nil_state_key
I18n.backend.store_translations(:en, {
:activerecord => {:state_machines => {:states => {:nil => 'empty'}}}
})

machine = StateMachine::Machine.new(@model)

assert_equal 'empty', machine.state(nil).human_name
end

def test_should_allow_customized_event_key_scoped_to_class_and_machine
I18n.backend.store_translations(:en, {
:activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
Expand Down
10 changes: 10 additions & 0 deletions test/unit/integrations/mongo_mapper_test.rb
Expand Up @@ -1574,6 +1574,16 @@ def test_should_allow_customized_state_key_unscoped
assert_equal 'shutdown', machine.state(:parked).human_name
end

def test_should_support_nil_state_key
I18n.backend.store_translations(:en, {
:mongo_mapper => {:state_machines => {:states => {:nil => 'empty'}}}
})

machine = StateMachine::Machine.new(@model)

assert_equal 'empty', machine.state(nil).human_name
end

def test_should_allow_customized_event_key_scoped_to_class_and_machine
I18n.backend.store_translations(:en, {
:mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
Expand Down
10 changes: 10 additions & 0 deletions test/unit/integrations/mongoid_test.rb
Expand Up @@ -1567,6 +1567,16 @@ def test_should_allow_customized_state_key_unscoped
assert_equal 'shutdown', machine.state(:parked).human_name
end

def test_should_support_nil_state_key
I18n.backend.store_translations(:en, {
:mongoid => {:state_machines => {:states => {:nil => 'empty'}}}
})

machine = StateMachine::Machine.new(@model)

assert_equal 'empty', machine.state(nil).human_name
end

def test_should_allow_customized_event_key_scoped_to_class_and_machine
I18n.backend.store_translations(:en, {
:mongoid => {:state_machines => {:'mongoid_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
Expand Down

0 comments on commit 83729ee

Please sign in to comment.