diff options
Diffstat (limited to 'activemodel/lib/active_model')
-rw-r--r-- | activemodel/lib/active_model/observer_array.rb | 98 | ||||
-rw-r--r-- | activemodel/lib/active_model/observing.rb | 33 |
2 files changed, 127 insertions, 4 deletions
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb new file mode 100644 index 0000000000..b8aa9cc1e2 --- /dev/null +++ b/activemodel/lib/active_model/observer_array.rb @@ -0,0 +1,98 @@ +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 + + private + + 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 transaction + orig_disabled_observers = disabled_observers.dup + + begin + yield + ensure + @disabled_observers = orig_disabled_observers + 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 + end + end + end +end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 3c80d584fe..e1a2ce218d 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -1,8 +1,10 @@ require 'singleton' +require 'active_model/observer_array' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/enumerable' module ActiveModel module Observing @@ -30,12 +32,12 @@ module ActiveModel # +instantiate_observers+ is called during startup, and before # each development request. def observers=(*values) - @observers = values.flatten + observers.replace(values.flatten) end # Gets the current observers. def observers - @observers ||= [] + @observers ||= ObserverArray.for(self) end # Gets the current observer instances. @@ -76,7 +78,11 @@ module ActiveModel elsif observer.respond_to?(:instance) observer.instance else - raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance" + raise ArgumentError, + "#{observer} must be a lowercase, underscored class name (or an " + + "instance of the class itself) responding to the instance " + + "method. Example: Person.observers = :big_brother # calls " + + "BigBrother.instance" end end @@ -197,6 +203,23 @@ module ActiveModel nil end end + + def subclasses + @subclasses ||= [] + end + + # List of all observer subclasses, sub-subclasses, etc. + # Necessary so we can disable or enable all observers. + def all_observers + subclasses.each_with_object(subclasses.dup) do |subclass, array| + array.concat(subclass.all_observers) + end + end + end + + def self.inherited(subclass) + subclasses << subclass + super end # Start observing the declared classes and their subclasses. @@ -210,7 +233,9 @@ module ActiveModel # Send observed_method(object) if the method exists. def update(observed_method, object) #:nodoc: - send(observed_method, object) if respond_to?(observed_method) + if respond_to?(observed_method) && ObserverArray.observer_enabled?(self, object) + send(observed_method, object) + end end # Special method sent by the observed class when it is inherited. |