aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model')
-rw-r--r--activemodel/lib/active_model/observer_array.rb98
-rw-r--r--activemodel/lib/active_model/observing.rb33
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.