Skip to content
This repository
Browse code

Fix observer callbacks being run when disabled in ActiveModel / Activ…

…eRecord integrations. Closes #162
  • Loading branch information...
commit db846629e735b241852ded803049f84fafbf0b4c 1 parent c99b406
Aaron Pfeifer obrie authored
1  CHANGELOG.md
Source Rendered
... ... @@ -1,5 +1,6 @@
1 1 # master
2 2
  3 +* Fix observer callbacks being run when disabled in ActiveModel / ActiveRecord integrations
3 4 * Add YARD integration for autogenerating documentation / embedding visualizations of state machines
4 5 * Allow states / events to be drawn with their human name instead of their internal name
5 6
5 lib/state_machine/integrations/active_model.rb
@@ -475,6 +475,7 @@ def load_locale
475 475 # Loads extensions to ActiveModel's Observers
476 476 def load_observer_extensions
477 477 require 'state_machine/integrations/active_model/observer'
  478 + require 'state_machine/integrations/active_model/observer_update'
478 479 end
479 480
480 481 # Adds a set of default callbacks that utilize the Observer extensions
@@ -568,14 +569,14 @@ def notify(type, object, transition)
568 569 ["_from_#{from}", nil].each do |from_segment|
569 570 ["_to_#{to}", nil].each do |to_segment|
570 571 object.class.changed if object.class.respond_to?(:changed)
571   - object.class.notify_observers('update_with_transition', [[event_segment, from_segment, to_segment].join, object, transition])
  572 + object.class.notify_observers('update_with_transition', ObserverUpdate.new([event_segment, from_segment, to_segment].join, object, transition))
572 573 end
573 574 end
574 575 end
575 576
576 577 # Generic updates
577 578 object.class.changed if object.class.respond_to?(:changed)
578   - object.class.notify_observers('update_with_transition', ["#{type}_transition", object, transition])
  579 + object.class.notify_observers('update_with_transition', ObserverUpdate.new("#{type}_transition", object, transition))
579 580
580 581 true
581 582 end
6 lib/state_machine/integrations/active_model/observer.rb
@@ -19,9 +19,9 @@ module ActiveModel
19 19 # end
20 20 # end
21 21 module Observer
22   - def update_with_transition(args)
23   - observed_method, object, transition = args
24   - send(observed_method, object, transition) if respond_to?(observed_method)
  22 + def update_with_transition(observer_update)
  23 + method = observer_update.method
  24 + send(method, *observer_update.args) if respond_to?(method)
25 25 end
26 26 end
27 27 end
42 lib/state_machine/integrations/active_model/observer_update.rb
... ... @@ -0,0 +1,42 @@
  1 +module StateMachine
  2 + module Integrations #:nodoc:
  3 + module ActiveModel
  4 + # Represents the encapsulation of all of the details to be included in an
  5 + # update to state machine observers. This allows multiple arguments to
  6 + # get passed to an observer method (instead of just a single +object+)
  7 + # while still respecting the way in which ActiveModel checks for the
  8 + # object's list of observers.
  9 + class ObserverUpdate
  10 + # The method to invoke on the observer
  11 + attr_reader :method
  12 +
  13 + # The object being transitioned
  14 + attr_reader :object
  15 +
  16 + # The transition being run
  17 + attr_reader :transition
  18 +
  19 + def initialize(method, object, transition) #:nodoc:
  20 + @method, @object, @transition = method, object, transition
  21 + end
  22 +
  23 + # The arguments to pass into the method
  24 + def args
  25 + [object, transition]
  26 + end
  27 +
  28 + # The class of the object being transitioned. Normally the object
  29 + # getting passed into observer methods is the actual instance of the
  30 + # ActiveModel class. ActiveModel uses that instance's class to check
  31 + # for enabled / disabled observers.
  32 + #
  33 + # Since state_machine is passing an ObserverUpdate instance into observer
  34 + # methods, +class+ needs to be overridden so that ActiveModel can still
  35 + # get access to the enabled / disabled observers.
  36 + def class
  37 + object.class
  38 + end
  39 + end
  40 + end
  41 + end
  42 +end
60 test/unit/integrations/active_model_test.rb
@@ -727,6 +727,40 @@ def test_should_be_valid_if_validation_succeeds_within_state_scope
727 727 end
728 728 end
729 729
  730 + class ObserverUpdateTest < BaseTestCase
  731 + def setup
  732 + @model = new_model { include ActiveModel::Observing }
  733 + @machine = StateMachine::Machine.new(@model)
  734 + @machine.state :parked, :idling
  735 + @machine.event :ignite
  736 +
  737 + @record = @model.new(:state => 'parked')
  738 + @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  739 +
  740 + @observer_update = StateMachine::Integrations::ActiveModel::ObserverUpdate.new(:before_transition, @record, @transition)
  741 + end
  742 +
  743 + def test_should_have_method
  744 + assert_equal :before_transition, @observer_update.method
  745 + end
  746 +
  747 + def test_should_have_object
  748 + assert_equal @record, @observer_update.object
  749 + end
  750 +
  751 + def test_should_have_transition
  752 + assert_equal @transition, @observer_update.transition
  753 + end
  754 +
  755 + def test_should_include_object_and_transition_in_args
  756 + assert_equal [@record, @transition], @observer_update.args
  757 + end
  758 +
  759 + def test_should_use_record_class_as_class
  760 + assert_equal @model, @observer_update.class
  761 + end
  762 + end
  763 +
730 764 class MachineWithObserversTest < BaseTestCase
731 765 def setup
732 766 @model = new_model { include ActiveModel::Observing }
@@ -750,7 +784,6 @@ def test_should_call_all_transition_callback_permutations
750 784 :before_transition
751 785 ]
752 786
753   - notified = false
754 787 observer = new_observer(@model) do
755 788 callbacks.each do |callback|
756 789 define_method(callback) do |*args|
@@ -765,6 +798,31 @@ def test_should_call_all_transition_callback_permutations
765 798 assert_equal callbacks, instance.notifications
766 799 end
767 800
  801 + def test_should_call_no_transition_callbacks_when_observers_disabled
  802 + return unless ::ActiveModel::VERSION::MAJOR >= 3 && ::ActiveModel::VERSION::MINOR >= 1
  803 +
  804 + callbacks = [
  805 + :before_ignite,
  806 + :before_transition
  807 + ]
  808 +
  809 + observer = new_observer(@model) do
  810 + callbacks.each do |callback|
  811 + define_method(callback) do |*args|
  812 + notifications << callback
  813 + end
  814 + end
  815 + end
  816 +
  817 + instance = observer.instance
  818 +
  819 + @model.observers.disable(observer) do
  820 + @transition.perform
  821 + end
  822 +
  823 + assert_equal [], instance.notifications
  824 + end
  825 +
768 826 def test_should_pass_record_and_transition_to_before_callbacks
769 827 observer = new_observer(@model) do
770 828 def before_transition(*args)
26 test/unit/integrations/active_record_test.rb
@@ -1571,7 +1571,6 @@ def test_should_call_all_transition_callback_permutations
1571 1571 :before_transition
1572 1572 ]
1573 1573
1574   - notified = false
1575 1574 observer = new_observer(@model) do
1576 1575 callbacks.each do |callback|
1577 1576 define_method(callback) do |*args|
@@ -1586,6 +1585,31 @@ def test_should_call_all_transition_callback_permutations
1586 1585 assert_equal callbacks, instance.notifications
1587 1586 end
1588 1587
  1588 + def test_should_call_no_transition_callbacks_when_observers_disabled
  1589 + return unless ::ActiveRecord::VERSION::MAJOR >= 3 && ::ActiveRecord::VERSION::MINOR >= 1
  1590 +
  1591 + callbacks = [
  1592 + :before_ignite,
  1593 + :before_transition
  1594 + ]
  1595 +
  1596 + observer = new_observer(@model) do
  1597 + callbacks.each do |callback|
  1598 + define_method(callback) do |*args|
  1599 + notifications << callback
  1600 + end
  1601 + end
  1602 + end
  1603 +
  1604 + instance = observer.instance
  1605 +
  1606 + @model.observers.disable(observer) do
  1607 + @transition.perform
  1608 + end
  1609 +
  1610 + assert_equal [], instance.notifications
  1611 + end
  1612 +
1589 1613 def test_should_pass_record_and_transition_to_before_callbacks
1590 1614 observer = new_observer(@model) do
1591 1615 def before_transition(*args)

0 comments on commit db84662

Please sign in to comment.
Something went wrong with that request. Please try again.