From 4d703592001eef1f7e670660cd701e32105bd521 Mon Sep 17 00:00:00 2001
From: Joshua Peek <josh@joshpeek.com>
Date: Wed, 10 Jun 2009 23:35:34 -0500
Subject: Integrate ActiveModel::Observing into ActiveRecord

---
 activemodel/lib/active_model.rb            |   9 +--
 activemodel/lib/active_model/observing.rb  | 108 +++++++++++++++++------------
 activemodel/test/cases/observing_test.rb   |  32 +++++----
 activerecord/lib/active_record.rb          |   2 +-
 activerecord/lib/active_record/base.rb     |   2 +-
 activerecord/lib/active_record/observer.rb |  95 ++-----------------------
 6 files changed, 93 insertions(+), 155 deletions(-)

diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index a3545f1d53..73cee9b88f 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -27,12 +27,13 @@ require 'active_support'
 
 module ActiveModel
   autoload :Base, 'active_model/base'
-  autoload :Observing, 'active_model/observing'
-  autoload :Validations, 'active_model/validations'
-  autoload :Errors, 'active_model/errors'
   autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
-  autoload :TestCase, 'active_model/test_case'
+  autoload :Errors, 'active_model/errors'
+  autoload :Observer, 'active_model/observing'
+  autoload :Observing, 'active_model/observing'
   autoload :StateMachine, 'active_model/state_machine'
+  autoload :TestCase, 'active_model/test_case'
+  autoload :Validations, 'active_model/validations'
   autoload :ValidationsRepairHelper, 'active_model/validations_repair_helper'
 end
 
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index d3c6d8e482..d01cb737e3 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -3,67 +3,80 @@ require 'singleton'
 
 module ActiveModel
   module Observing
+    extend ActiveSupport::Concern
+
+    included do
+      extend Observable
+    end
+
     module ClassMethods
-      def observers
-        @observers ||= []
-      end
-      
+      # Activates the observers assigned. Examples:
+      #
+      #   # Calls PersonObserver.instance
+      #   ActiveRecord::Base.observers = :person_observer
+      #
+      #   # Calls Cacher.instance and GarbageCollector.instance
+      #   ActiveRecord::Base.observers = :cacher, :garbage_collector
+      #
+      #   # Same as above, just using explicit class references
+      #   ActiveRecord::Base.observers = Cacher, GarbageCollector
+      #
+      # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
+      # called during startup, and before each development request.
       def observers=(*values)
         @observers = values.flatten
       end
-      
+
+      # Gets the current observers.
+      def observers
+        @observers ||= []
+      end
+
+      # Instantiate the global Active Record observers.
       def instantiate_observers
         observers.each { |o| instantiate_observer(o) }
       end
-    
-    protected
-      def instantiate_observer(observer)
-        # string/symbol
-        if observer.respond_to?(:to_sym)
-          observer = observer.to_s.camelize.constantize.instance
-        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"
+
+      protected
+        def instantiate_observer(observer)
+          # string/symbol
+          if observer.respond_to?(:to_sym)
+            observer = observer.to_s.camelize.constantize.instance
+          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"
+          end
+        end
+
+        # Notify observers when the observed class is subclassed.
+        def inherited(subclass)
+          super
+          changed
+          notify_observers :observed_class_inherited, subclass
         end
-      end
-      
-      # Notify observers when the observed class is subclassed.
-      def inherited(subclass)
-        super
-        changed
-        notify_observers :observed_class_inherited, subclass
-      end
-    end
-    
-    def self.included(receiver)
-      receiver.extend Observable, ClassMethods
     end
   end
 
   class Observer
     include Singleton
-    attr_writer :observed_classes
 
     class << self
-      attr_accessor :models
       # Attaches the observer to the supplied model classes.
       def observe(*models)
-        @models = models.flatten
-        @models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
+        models.flatten!
+        models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
+        define_method(:observed_classes) { models }
       end
 
-      def observed_class_name
-        @observed_class_name ||= 
-          if guessed_name = name.scan(/(.*)Observer/)[0]
-            @observed_class_name = guessed_name[0]
-          end
+      def observed_classes
+        Array.wrap(observed_class)
       end
 
       # The class observed by default is inferred from the observer's class name:
-      #   assert_equal [Person], PersonObserver.observed_class
+      #   assert_equal Person, PersonObserver.observed_class
       def observed_class
-        if observed_class_name
+        if observed_class_name = name[/(.*)Observer/, 1]
           observed_class_name.constantize
         else
           nil
@@ -73,8 +86,11 @@ module ActiveModel
 
     # Start observing the declared classes and their subclasses.
     def initialize
-      self.observed_classes = self.class.models if self.class.models
-      observed_classes.each { |klass| klass.add_observer(self) }
+      observed_classes.each { |klass| add_observer!(klass) }
+    end
+
+    def observed_classes
+      self.class.observed_classes
     end
 
     # Send observed_method(object) if the method exists.
@@ -86,12 +102,12 @@ module ActiveModel
     # Passes the new subclass.
     def observed_class_inherited(subclass) #:nodoc:
       self.class.observe(observed_classes + [subclass])
-      subclass.add_observer(self)
+      add_observer!(subclass)
     end
 
-  protected
-    def observed_classes
-      @observed_classes ||= [self.class.observed_class]
-    end
+    protected
+      def add_observer!(klass)
+        klass.add_observer(self)
+      end
   end
-end
\ No newline at end of file
+end
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index 421ac4b4f8..564451fa2f 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -9,7 +9,7 @@ class FooObserver < ActiveModel::Observer
   class << self
     public :new
   end
-  
+
   attr_accessor :stub
 
   def on_spec(record)
@@ -28,12 +28,12 @@ class ObservingTest < ActiveModel::TestCase
   test "initializes model with no cached observers" do
     assert ObservedModel.observers.empty?, "Not empty: #{ObservedModel.observers.inspect}"
   end
-  
+
   test "stores cached observers in an array" do
     ObservedModel.observers << :foo
     assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
   end
-  
+
   test "flattens array of assigned cached observers" do
     ObservedModel.observers = [[:foo], :bar]
     assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
@@ -57,22 +57,30 @@ class ObservingTest < ActiveModel::TestCase
     ObservedModel::Observer.expects(:instance)
     ObservedModel.instantiate_observers
   end
-  
+
   test "passes observers to subclasses" do
     FooObserver.instance
     bar = Class.new(Foo)
     assert_equal Foo.count_observers, bar.count_observers
   end
 end
-  
+
 class ObserverTest < ActiveModel::TestCase
   def setup
     ObservedModel.observers = :foo_observer
-    FooObserver.models = nil
+    FooObserver.instance_eval do
+      alias_method :original_observed_classes, :observed_classes
+    end
+  end
+
+  def teardown
+    FooObserver.instance_eval do
+      alias_method :observed_classes, :original_observed_classes
+    end
   end
 
   test "guesses implicit observable model name" do
-    assert_equal 'Foo', FooObserver.observed_class_name
+    assert_equal Foo, FooObserver.observed_class
   end
 
   test "tracks implicit observable models" do
@@ -80,7 +88,7 @@ class ObserverTest < ActiveModel::TestCase
     assert  instance.send(:observed_classes).include?(Foo), "Foo not in #{instance.send(:observed_classes).inspect}"
     assert !instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{instance.send(:observed_classes).inspect}"
   end
-  
+
   test "tracks explicit observed model class" do
     old_instance = FooObserver.new
     assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
@@ -88,7 +96,7 @@ class ObserverTest < ActiveModel::TestCase
     instance = FooObserver.new
     assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
   end
-  
+
   test "tracks explicit observed model as string" do
     old_instance = FooObserver.new
     assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
@@ -96,7 +104,7 @@ class ObserverTest < ActiveModel::TestCase
     instance = FooObserver.new
     assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
   end
-  
+
   test "tracks explicit observed model as symbol" do
     old_instance = FooObserver.new
     assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
@@ -104,7 +112,7 @@ class ObserverTest < ActiveModel::TestCase
     instance = FooObserver.new
     assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
   end
-  
+
   test "calls existing observer event" do
     foo = Foo.new
     FooObserver.instance.stub = stub
@@ -112,7 +120,7 @@ class ObserverTest < ActiveModel::TestCase
     Foo.send(:changed)
     Foo.send(:notify_observers, :on_spec, foo)
   end
-  
+
   test "skips nonexistent observer event" do
     foo = Foo.new
     Foo.send(:changed)
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 0ad7b02785..7ffabc210e 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -59,7 +59,7 @@ module ActiveRecord
   autoload :Migrator, 'active_record/migration'
   autoload :NamedScope, 'active_record/named_scope'
   autoload :NestedAttributes, 'active_record/nested_attributes'
-  autoload :Observing, 'active_record/observer'
+  autoload :Observer, 'active_record/observer'
   autoload :QueryCache, 'active_record/query_cache'
   autoload :Reflection, 'active_record/reflection'
   autoload :Schema, 'active_record/schema'
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 98898e9c18..c4d143ab05 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -3149,7 +3149,7 @@ module ActiveRecord #:nodoc:
     include Locking::Optimistic, Locking::Pessimistic
     include AttributeMethods
     include Dirty
-    include Callbacks, Observing, Timestamp
+    include Callbacks, ActiveModel::Observing, Timestamp
     include Associations, AssociationPreload, NamedScope
 
     # AutosaveAssociation needs to be included before Transactions, because we want
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 89ec0962bf..a34ff4a47a 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -2,56 +2,6 @@ require 'singleton'
 require 'set'
 
 module ActiveRecord
-  module Observing # :nodoc:
-    extend ActiveSupport::Concern
-
-    module ClassMethods
-      # Activates the observers assigned. Examples:
-      #
-      #   # Calls PersonObserver.instance
-      #   ActiveRecord::Base.observers = :person_observer
-      #
-      #   # Calls Cacher.instance and GarbageCollector.instance
-      #   ActiveRecord::Base.observers = :cacher, :garbage_collector
-      #
-      #   # Same as above, just using explicit class references
-      #   ActiveRecord::Base.observers = Cacher, GarbageCollector
-      #
-      # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
-      # called during startup, and before each development request.
-      def observers=(*observers)
-        @observers = observers.flatten
-      end
-
-      # Gets the current observers.
-      def observers
-        @observers ||= []
-      end
-
-      # Instantiate the global Active Record observers.
-      def instantiate_observers
-        return if @observers.blank?
-        @observers.each do |observer|
-          if observer.respond_to?(:to_sym) # Symbol or String
-            observer.to_s.camelize.constantize.instance
-          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"
-          end
-        end
-      end
-
-      protected
-        # Notify observers when the observed class is subclassed.
-        def inherited(subclass)
-          super
-          changed
-          notify_observers :observed_class_inherited, subclass
-        end
-    end
-  end
-
   # Observer classes respond to lifecycle callbacks to implement trigger-like
   # behavior outside the original class. This is a great way to reduce the
   # clutter that normally comes when the model class is burdened with
@@ -137,56 +87,19 @@ module ActiveRecord
   # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
   # singletons and that call instantiates and registers them.
   #
-  class Observer
-    include Singleton
-
-    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) }
-      end
-
-      # The class observed by default is inferred from the observer's class name:
-      #   assert_equal Person, PersonObserver.observed_class
-      def observed_class
-        if observed_class_name = name[/(.*)Observer/, 1]
-          observed_class_name.constantize
-        else
-          nil
-        end
-      end
-    end
-
-    # Start observing the declared classes and their subclasses.
+  class Observer < ActiveModel::Observer
     def initialize
-      Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
-    end
-
-    # Send observed_method(object) if the method exists.
-    def update(observed_method, object) #:nodoc:
-      send(observed_method, object) if respond_to?(observed_method)
-    end
-
-    # Special method sent by the observed class when it is inherited.
-    # Passes the new subclass.
-    def observed_class_inherited(subclass) #:nodoc:
-      self.class.observe(observed_classes + [subclass])
-      add_observer!(subclass)
+      super
+      observed_subclasses.each { |klass| add_observer!(klass) }
     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)
+        super
         if respond_to?(:after_find) && !klass.method_defined?(:after_find)
           klass.class_eval 'def after_find() end'
         end
-- 
cgit v1.2.3