aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/observer_array.rb
blob: 8de6918d1801946386e4a2c3c0756ef616f7bfef (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
require 'set'

module ActiveModel
  # Stores the enabled/disabled state of individual observers for
  # a particular model class.
  class ObserverArray < Array
    attr_reader :model_class
    def initialize(model_class, *args)
      @model_class = model_class
      super(*args)
    end

    # Returns true if the given observer is disabled for the model class.
    def disabled_for?(observer)
      disabled_observers.include?(observer.class)
    end

    # Disables one or more observers. This supports multiple forms:
    #
    #   ORM.observers.disable :all
    #     # => disables all observers for all models subclassed from 
    #     #    an ORM base class that includes ActiveModel::Observing
    #     #    e.g. ActiveRecord::Base
    #
    #   ORM.observers.disable :user_observer
    #     # => disables the UserObserver
    #
    #   User.observers.disable AuditTrail
    #     # => disables the AuditTrail observer for User notifications.
    #     #    Other models will still notify the AuditTrail observer.
    #
    #   ORM.observers.disable :observer_1, :observer_2
    #     # => disables Observer1 and Observer2 for all models.
    #
    #   User.observers.disable :all do
    #     # all user observers are disabled for
    #     # just the duration of the block
    #   end
    def disable(*observers, &block)
      set_enablement(false, observers, &block)
    end

    # Enables one or more observers. This supports multiple forms:
    #
    #   ORM.observers.enable :all
    #     # => enables all observers for all models subclassed from 
    #     #    an ORM base class that includes ActiveModel::Observing
    #     #    e.g. ActiveRecord::Base
    #
    #   ORM.observers.enable :user_observer
    #     # => enables the UserObserver
    #
    #   User.observers.enable AuditTrail
    #     # => enables the AuditTrail observer for User notifications.
    #     #    Other models will not be affected (i.e. they will not
    #     #    trigger notifications to AuditTrail if previously disabled)
    #
    #   ORM.observers.enable :observer_1, :observer_2
    #     # => enables Observer1 and Observer2 for all models.
    #
    #   User.observers.enable :all do
    #     # all user observers are enabled for
    #     # just the duration of the block
    #   end
    #
    # Note: all observers are enabled by default. This method is only
    # useful when you have previously disabled one or more observers.
    def enable(*observers, &block)
      set_enablement(true, observers, &block)
    end

    protected

      def disabled_observers
        @disabled_observers ||= Set.new
      end

      def observer_class_for(observer)
        return observer if observer.is_a?(Class)

        if observer.respond_to?(:to_sym) # string/symbol
          observer.to_s.camelize.constantize
        else
          raise ArgumentError, "#{observer} was not a class or a " +
            "lowercase, underscored class name as expected."
        end
      end

      def start_transaction
        disabled_observer_stack.push(disabled_observers.dup)
        each_subclass_array do |array|
          array.start_transaction
        end
      end

      def disabled_observer_stack
        @disabled_observer_stack ||= []
      end

      def end_transaction
        @disabled_observers = disabled_observer_stack.pop
        each_subclass_array do |array|
          array.end_transaction
        end
      end

      def transaction
        start_transaction

        begin
          yield
        ensure
          end_transaction
        end
      end

      def each_subclass_array
        model_class.descendants.each do |subclass|
          yield subclass.observers
        end
      end

      def set_enablement(enabled, observers)
        if block_given?
          transaction do
            set_enablement(enabled, observers)
            yield
          end
        else
          observers = ActiveModel::Observer.descendants if observers == [:all]
          observers.each do |obs|
            klass = observer_class_for(obs)

            unless klass < ActiveModel::Observer
              raise ArgumentError.new("#{obs} does not refer to a valid observer")
            end

            if enabled
              disabled_observers.delete(klass)
            else
              disabled_observers << klass
            end
          end

          each_subclass_array do |array|
            array.set_enablement(enabled, observers)
          end
        end
      end
  end
end