Created
May 14, 2011 02:02
-
-
Save AquaGeek/971599 to your computer and use it in GitHub Desktop.
Rails Lighthouse ticket #1639
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 9616243694718347d19bcf9b25dbe69260cbb845 Mon Sep 17 00:00:00 2001 | |
From: Gabriel Gironda <[email protected]> | |
Date: Tue, 27 Jan 2009 16:33:54 -0600 | |
Subject: [PATCH] Adds an :on and :after/:before option to AR observers | |
--- | |
activerecord/lib/active_record/observer.rb | 89 ++++++++++++++++++++++------ | |
activerecord/test/cases/observer_test.rb | 78 ++++++++++++++++++++++++ | |
2 files changed, 148 insertions(+), 19 deletions(-) | |
create mode 100644 activerecord/test/cases/observer_test.rb | |
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb | |
index b35e407..c16284e 100644 | |
--- a/activerecord/lib/active_record/observer.rb | |
+++ b/activerecord/lib/active_record/observer.rb | |
@@ -107,6 +107,21 @@ module ActiveRecord | |
# | |
# The AuditObserver will now act on both updates to Account and Balance by treating them both as records. | |
# | |
+ # If the audit observer only needs to watch a separate set of observable object updates on each kind of model, the | |
+ # observed updates can be specified via the :on option to observe | |
+ # | |
+ # class AuditObserver < ActiveRecord::Observer | |
+ # observe :account, :after => [:create, :update] | |
+ # observe :balance, :on => :after_save | |
+ # | |
+ # def after_update(record) | |
+ # AuditTrail.new(record, "UPDATED") | |
+ # end | |
+ # | |
+ # def after_create(record); '...'; end | |
+ # def after_save(record); '...'; end | |
+ # end | |
+ # | |
# == Available callback methods | |
# | |
# The observer can implement callback methods for each of the methods described in the Callbacks module. | |
@@ -141,13 +156,17 @@ module ActiveRecord | |
# | |
class Observer | |
include Singleton | |
- | |
+ write_inheritable_hash :observable_updates, {} | |
+ | |
class << self | |
+ | |
# Attaches the observer to the supplied model classes. | |
def observe(*models) | |
- models.flatten! | |
- models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model } | |
- define_method(:observed_classes) { Set.new(models) } | |
+ options = models.extract_options! | |
+ set_watch_options(models, options) | |
+ models = constantize_models(models) | |
+ already_observed = read_inheritable_attribute(:observed_classes) | |
+ write_inheritable_attribute(:observed_classes, Set.new(Array(already_observed)) + Set.new(models)) | |
end | |
# The class observed by default is inferred from the observer's class name: | |
@@ -159,6 +178,28 @@ module ActiveRecord | |
nil | |
end | |
end | |
+ | |
+ private | |
+ | |
+ def set_watch_options(models, options) | |
+ on_methods = options.inject([]) do |methods,(key,val)| | |
+ if [:before, :after].include?(key) | |
+ methods.concat(Array(val).map {|v| :"#{key}_#{v}"}) | |
+ elsif key == :on | |
+ methods.concat(Array(val).map(&:to_sym)) | |
+ end | |
+ methods | |
+ end | |
+ constantize_models(models).each do |model| | |
+ existing = (obs = read_inheritable_attribute(:observable_updates)) ? obs[model] : [] | |
+ write_inheritable_hash(:observable_updates, model => Array(existing) + on_methods) | |
+ end | |
+ end | |
+ | |
+ def constantize_models(models) | |
+ models.flatten.collect { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model } | |
+ end | |
+ | |
end | |
# Start observing the declared classes and their subclasses. | |
@@ -168,7 +209,7 @@ module ActiveRecord | |
# Send observed_method(object) if the method exists. | |
def update(observed_method, object) #:nodoc: | |
- send(observed_method, object) if respond_to?(observed_method) | |
+ send(observed_method, object) if listening_for?(object, observed_method) | |
end | |
# Special method sent by the observed class when it is inherited. | |
@@ -177,21 +218,31 @@ module ActiveRecord | |
self.class.observe(observed_classes + [subclass]) | |
add_observer!(subclass) | |
end | |
+ | |
+ def observed_classes | |
+ self.class.read_inheritable_attribute(:observed_classes) || Set.new([self.class.observed_class].compact.flatten) | |
+ end | |
+ | |
+ protected | |
+ | |
+ def listening_for?(object, observed_method) | |
+ respond_to?(observed_method) && watching?(object, observed_method) | |
+ end | |
+ | |
+ def watching?(object, observed_method) | |
+ observed = self.class.read_inheritable_attribute(:observable_updates) | |
+ observed[object.class].blank? || observed[object.class].include?(observed_method.to_sym) | |
+ end | |
+ | |
+ def observed_subclasses | |
+ observed_classes.sum([]) { |klass| klass.send(:subclasses) } | |
+ end | |
- protected | |
- def observed_classes | |
- Set.new([self.class.observed_class].compact.flatten) | |
- end | |
- | |
- def observed_subclasses | |
- observed_classes.sum([]) { |klass| klass.send(:subclasses) } | |
- end | |
- | |
- def add_observer!(klass) | |
- klass.add_observer(self) | |
- if respond_to?(:after_find) && !klass.method_defined?(:after_find) | |
- klass.class_eval 'def after_find() end' | |
- end | |
+ def add_observer!(klass) | |
+ klass.add_observer(self) | |
+ if respond_to?(:after_find) && !klass.method_defined?(:after_find) | |
+ klass.class_eval 'def after_find() end' | |
end | |
+ end | |
end | |
end | |
diff --git a/activerecord/test/cases/observer_test.rb b/activerecord/test/cases/observer_test.rb | |
new file mode 100644 | |
index 0000000..515b7fe | |
--- /dev/null | |
+++ b/activerecord/test/cases/observer_test.rb | |
@@ -0,0 +1,78 @@ | |
+require "cases/helper" | |
+require 'models/topic' | |
+require 'models/developer' | |
+require 'models/reply' | |
+ | |
+class ActivityObserver < ActiveRecord::Observer | |
+ observe :topic, :after => [:create, :destroy], :before => :update | |
+ observe :topic, :developer, :on => [:before_destroy, 'before_save'] | |
+ observe :reply | |
+end | |
+ | |
+class ObserverTest < ActiveRecord::TestCase | |
+ | |
+ def setup | |
+ @observer = ActivityObserver.instance | |
+ end | |
+ | |
+ lambda do # with topic | |
+ topic = Topic.new | |
+ | |
+ test "should observe after_create on Topic" do | |
+ @observer.expects(:after_create).with(topic) | |
+ topic.send(:notify, :after_create) | |
+ end | |
+ | |
+ test "should observe after_destroy on Topic" do | |
+ @observer.expects(:after_destroy).with(topic) | |
+ topic.send(:notify, :after_destroy) | |
+ end | |
+ | |
+ test "should observe before_update on Topic" do | |
+ @observer.expects(:before_update).with(topic) | |
+ topic.send(:notify, :before_update) | |
+ end | |
+ | |
+ test "should observe before_destroy on Topic" do | |
+ @observer.expects(:before_destroy).with(topic) | |
+ topic.send(:notify, :before_destroy) | |
+ end | |
+ | |
+ test "should observe before_save on Topic" do | |
+ @observer.expects(:before_save).with(topic) | |
+ topic.send(:notify, :before_save) | |
+ end | |
+ | |
+ test "should ignore after_save and after_validation on topic" do | |
+ @observer.expects(:after_save).with(topic).never | |
+ @observer.expects(:after_validation).with(topic).never | |
+ topic.send(:notify, :after_save) | |
+ topic.send(:notify, :after_validation) | |
+ end | |
+ | |
+ end.call | |
+ | |
+ lambda do # with developer | |
+ developer = Developer.new | |
+ | |
+ test "should observe before_destroy on Developer" do | |
+ @observer.expects(:before_destroy).with(developer) | |
+ developer.send(:notify, :before_destroy) | |
+ end | |
+ | |
+ test "should observe before_save on Developer" do | |
+ @observer.expects(:before_save).with(developer) | |
+ developer.send(:notify, :before_save) | |
+ end | |
+ | |
+ end.call | |
+ | |
+ test "should observe all callbacks on Reply" do | |
+ reply = Reply.new | |
+ ActiveRecord::Callbacks::CALLBACKS.each do |callback| | |
+ @observer.expects(callback).with(reply) | |
+ reply.send(:notify, callback) | |
+ end | |
+ end | |
+ | |
+end | |
-- | |
1.6.0.5 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment