aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/observer_array.rb
blob: f3b5811b8176be3549f2b069eabc3d40b7ce72da (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
require 'set'

module ActiveModel
  # Stores the enabled/disabled state of individual observers for
  # a particular model classes.
  class ObserverArray < Array
    INSTANCES = Hash.new do |hash, model_class|
      hash[model_class] = new(model_class)
    end

    def self.for(model_class)
      return nil unless model_class < ActiveModel::Observing
      INSTANCES[model_class]
    end

    # returns false if:
    #   - the ObserverArray for the given model's class has the given observer
    #     in its disabled_observers set.
    #   - or that is the case at any level of the model's superclass chain.
    def self.observer_enabled?(observer, model)
      klass = model.class
      observer_class = observer.class

      loop do
        break unless array = self.for(klass)
        return false if array.disabled_observers.include?(observer_class)
        klass = klass.superclass
      end

      true # observers are enabled by default
    end

    def disabled_observers
      @disabled_observers ||= Set.new
    end

    attr_reader :model_class
    def initialize(model_class, *args)
      @model_class = model_class
      super(*args)
    end

    def disable(*observers, &block)
      set_enablement(false, observers, &block)
    end

    def enable(*observers, &block)
      set_enablement(true, observers, &block)
    end

    protected

      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.subclasses.each do |subclass|
          yield self.class.for(subclass)
        end
      end

      def set_enablement(enabled, observers)
        if block_given?
          transaction do
            set_enablement(enabled, observers)
            yield
          end
        else
          observers = ActiveModel::Observer.all_observers 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