提交 1f8cc446 编写于 作者: M Myron Marston 提交者: David Heinemeier Hansson

Allow observers to be enabled and disabled.

This is useful in situations like model unit tests and the occasional rake task to backfill old data.
上级 5d20c0a6
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
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 ClassMethods
# +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.
......@@ -201,6 +203,23 @@ def observed_class
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.
......@@ -214,7 +233,9 @@ def observed_classes #:nodoc:
# 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.
......
require 'cases/helper'
require 'models/observers'
class ObserverArrayTest < ActiveModel::TestCase
def teardown
ORM.observers.enable :all
Budget.observers.enable :all
Widget.observers.enable :all
end
def assert_observer_notified(model_class, observer_class)
observer_class.instance.before_save_invocations.clear
model_instance = model_class.new
model_instance.save
assert_equal [model_instance], observer_class.instance.before_save_invocations
end
def assert_observer_not_notified(model_class, observer_class)
observer_class.instance.before_save_invocations.clear
model_instance = model_class.new
model_instance.save
assert_equal [], observer_class.instance.before_save_invocations
end
test "all observers are enabled by default" do
assert_observer_notified Widget, WidgetObserver
assert_observer_notified Budget, BudgetObserver
assert_observer_notified Widget, AuditTrail
assert_observer_notified Budget, AuditTrail
end
test "can disable individual observers using a class constant" do
ORM.observers.disable WidgetObserver
assert_observer_not_notified Widget, WidgetObserver
assert_observer_notified Budget, BudgetObserver
assert_observer_notified Widget, AuditTrail
assert_observer_notified Budget, AuditTrail
end
test "can disable individual observers using a symbol" do
ORM.observers.disable :budget_observer
assert_observer_notified Widget, WidgetObserver
assert_observer_not_notified Budget, BudgetObserver
assert_observer_notified Widget, AuditTrail
assert_observer_notified Budget, AuditTrail
end
test "can disable all observers using :all" do
ORM.observers.disable :all
assert_observer_not_notified Widget, WidgetObserver
assert_observer_not_notified Budget, BudgetObserver
assert_observer_not_notified Widget, AuditTrail
assert_observer_not_notified Budget, AuditTrail
end
test "can disable observers on individual models without affecting observers on other models" do
Widget.observers.disable :all
assert_observer_not_notified Widget, WidgetObserver
assert_observer_notified Budget, BudgetObserver
assert_observer_not_notified Widget, AuditTrail
assert_observer_notified Budget, AuditTrail
end
test "can disable observers for the duration of a block" do
yielded = false
ORM.observers.disable :budget_observer do
yielded = true
assert_observer_notified Widget, WidgetObserver
assert_observer_not_notified Budget, BudgetObserver
assert_observer_notified Widget, AuditTrail
assert_observer_notified Budget, AuditTrail
end
assert yielded
assert_observer_notified Widget, WidgetObserver
assert_observer_notified Budget, BudgetObserver
assert_observer_notified Widget, AuditTrail
assert_observer_notified Budget, AuditTrail
end
test "can enable observers for the duration of a block" do
yielded = false
Widget.observers.disable :all
Widget.observers.enable :all do
yielded = true
assert_observer_notified Widget, WidgetObserver
assert_observer_notified Budget, BudgetObserver
assert_observer_notified Widget, AuditTrail
assert_observer_notified Budget, AuditTrail
end
assert yielded
assert_observer_not_notified Widget, WidgetObserver
assert_observer_notified Budget, BudgetObserver
assert_observer_not_notified Widget, AuditTrail
assert_observer_notified Budget, AuditTrail
end
test "raises an appropriate error when a developer accidentally enables or disables the wrong class (i.e. Widget instead of WidgetObserver)" do
assert_raise ArgumentError do
ORM.observers.enable :widget
end
assert_raise ArgumentError do
ORM.observers.enable Widget
end
assert_raise ArgumentError do
ORM.observers.disable :widget
end
assert_raise ArgumentError do
ORM.observers.disable Widget
end
end
end
......@@ -43,6 +43,11 @@ def setup
assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}"
end
test "uses an ObserverArray so observers can be disabled" do
ObservedModel.observers = [:foo, :bar]
assert ObservedModel.observers.is_a?(ActiveModel::ObserverArray)
end
test "instantiates observer names passed as strings" do
ObservedModel.observers << 'foo_observer'
FooObserver.expects(:instance)
......
class ORM
include ActiveModel::Observing
def save
notify_observers :before_save
end
class Observer < ActiveModel::Observer
def before_save_invocations
@before_save_invocations ||= []
end
def before_save(record)
before_save_invocations << record
end
end
end
class Widget < ORM; end
class Budget < ORM; end
class WidgetObserver < ORM::Observer; end
class BudgetObserver < ORM::Observer; end
class AuditTrail < ORM::Observer
observe :widget, :budget
end
ORM.instantiate_observers
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册