Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 4521

Show
Ignore:
Timestamp:
06/30/06 04:38:24 (4 years ago)
Author:
bitsweat
Message:

r4738@asus: jeremy | 2006-06-29 20:18:43 -0700
Observers also watch subclasses created after they are declared. Closes #5535.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/CHANGELOG

    r4511 r4521  
    11*SVN* 
     2 
     3* Observers also watch subclasses created after they are declared. #5535 [daniels@pronto.com.au] 
    24 
    35* Removed deprecated timestamps_gmt class methods. [Jeremy Kemper] 
  • trunk/activerecord/lib/active_record/observer.rb

    r4310 r4521  
    11require 'singleton' 
     2require 'set' 
    23 
    34module ActiveRecord 
     
    1314      #   ActiveRecord::Base.observers = :person_observer 
    1415      # 
    15       #   # Calls Cacher.instance and GarbageCollector.instance  
     16      #   # Calls Cacher.instance and GarbageCollector.instance 
    1617      #   ActiveRecord::Base.observers = :cacher, :garbage_collector 
    1718      # 
     
    1920      #   ActiveRecord::Base.observers = Cacher, GarbageCollector 
    2021      def observers=(*observers) 
    21         observers = [ observers ].flatten.each do |observer|  
    22           observer.is_a?(Symbol) ?  
    23             observer.to_s.camelize.constantize.instance : 
     22        observers.flatten.each do |observer| 
     23          if observer.respond_to?(:to_sym) # Symbol or String 
     24            observer.to_s.camelize.constantize.instance 
     25          elsif observer.respond_to?(:instance) 
    2426            observer.instance 
     27          else 
     28            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" 
     29          end 
    2530        end 
    2631      end 
     32 
     33      protected 
     34        # Notify observers when the observed class is subclassed. 
     35        def inherited(subclass) 
     36          super 
     37          changed 
     38          notify_observers :observed_class_inherited, subclass 
     39        end 
    2740    end 
    2841  end 
     
    8598  # 
    8699  # == Storing Observers in Rails 
    87   #  
     100  # 
    88101  # If you're using Active Record within Rails, observer classes are usually stored in app/models with the 
    89102  # naming convention of app/models/audit_observer.rb. 
    90103  # 
    91104  # == Configuration 
    92   #  
     105  # 
    93106  # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your 
    94107  # <tt>config/environment.rb</tt> file. 
     
    104117    # when Dependencies.mechanism = :load. 
    105118    include Reloadable::Subclasses 
    106      
    107     # Attaches the observer to the supplied model classes. 
    108     def self.observe(*models) 
    109       define_method(:observed_class) { models } 
     119 
     120    class << self 
     121      # Attaches the observer to the supplied model classes. 
     122      def observe(*models) 
     123        define_method(:observed_classes) { Set.new(models) } 
     124      end 
     125 
     126      # The class observed by default is inferred from the observer's class name: 
     127      #   assert_equal [Person], PersonObserver.observed_class 
     128      def observed_class 
     129        name.scan(/(.*)Observer/)[0][0].constantize 
     130      end 
    110131    end 
    111132 
     133    # Start observing the declared classes and their subclasses. 
    112134    def initialize 
    113       observed_classes = [ observed_class ].flatten 
    114       observed_subclasses_class = observed_classes.collect {|c| c.send(:subclasses) }.flatten! 
    115       (observed_classes + observed_subclasses_class).each do |klass|  
     135      Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass } 
     136    end 
     137 
     138    # Send observed_method(object) if the method exists. 
     139    def update(observed_method, object) #:nodoc: 
     140      send(observed_method, object) if respond_to?(observed_method) 
     141    end 
     142 
     143    # Special method sent by the observed class when it is inherited. 
     144    # Passes the new subclass. 
     145    def observed_class_inherited(subclass) #:nodoc: 
     146      self.class.observe(observed_classes + [subclass]) 
     147      add_observer!(subclass) 
     148    end 
     149 
     150    protected 
     151      def observed_classes 
     152        Set.new([self.class.observed_class].flatten) 
     153      end 
     154 
     155      def observed_subclasses 
     156        observed_classes.sum(&:subclasses) 
     157      end 
     158 
     159      def add_observer!(klass) 
    116160        klass.add_observer(self) 
    117         klass.send(:define_method, :after_find) unless klass.respond_to?(:after_find) 
    118       end 
    119     end 
    120    
    121     def update(callback_method, object) #:nodoc: 
    122       send(callback_method, object) if respond_to?(callback_method) 
    123     end 
    124      
    125     private 
    126       def observed_class 
    127         if self.class.respond_to? "observed_class" 
    128           self.class.observed_class 
    129         else 
    130           Object.const_get(infer_observed_class_name) 
    131         end 
    132       end 
    133        
    134       def infer_observed_class_name 
    135         self.class.name.scan(/(.*)Observer/)[0][0] 
     161        klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find) 
    136162      end 
    137163  end 
  • trunk/activerecord/test/lifecycle_test.rb

    r2327 r4521  
    4747class MultiObserver < ActiveRecord::Observer 
    4848  attr_reader :record 
    49    
     49 
    5050  def self.observed_class() [ Topic, Developer ] end 
     51 
     52  cattr_reader :last_inherited 
     53  @@last_inherited = nil 
     54 
     55  def observed_class_inherited_with_testing(subclass) 
     56    observed_class_inherited_without_testing(subclass) 
     57    @@last_inherited = subclass 
     58  end 
     59 
     60  alias_method_chain :observed_class_inherited, :testing 
    5161 
    5262  def after_find(record) 
    5363    @record = record 
    5464  end 
    55  
    5665end 
    5766 
     
    6473    assert_equal 0, Topic.count 
    6574  end 
    66    
     75 
    6776  def test_after_save 
    6877    ActiveRecord::Base.observers = :topic_manual_observer 
     
    7180    topic.title = "hello" 
    7281    topic.save 
    73      
     82 
    7483    assert TopicManualObserver.instance.has_been_notified? 
    7584    assert_equal :after_save, TopicManualObserver.instance.callbacks.last["callback_method"] 
    7685  end 
    77    
     86 
    7887  def test_observer_update_on_save 
    7988    ActiveRecord::Base.observers = TopicManualObserver 
    8089 
    81     topic = Topic.find(1)     
     90    topic = Topic.find(1) 
    8291    assert TopicManualObserver.instance.has_been_notified? 
    8392    assert_equal :after_find, TopicManualObserver.instance.callbacks.first["callback_method"] 
    8493  end 
    85    
     94 
    8695  def test_auto_observer 
    8796    topic_observer = TopicaObserver.instance 
    8897 
    89     topic = Topic.find(1)     
    90     assert_equal topic_observer.topic.title, topic.title 
     98    topic = Topic.find(1) 
     99    assert_equal topic.title, topic_observer.topic.title 
    91100  end 
    92    
    93   def test_infered_auto_observer 
     101 
     102  def test_inferred_auto_observer 
    94103    topic_observer = TopicObserver.instance 
    95104 
    96     topic = Topic.find(1)     
    97     assert_equal topic_observer.topic.title, topic.title 
     105    topic = Topic.find(1) 
     106    assert_equal topic.title, topic_observer.topic.title 
    98107  end 
    99    
     108 
    100109  def test_observing_two_classes 
    101110    multi_observer = MultiObserver.instance 
    102111 
    103112    topic = Topic.find(1) 
    104     assert_equal multi_observer.record.title, topic.title 
     113    assert_equal topic.title, multi_observer.record.title 
    105114 
    106     developer = Developer.find(1)     
    107     assert_equal multi_observer.record.name, developer.name 
     115    developer = Developer.find(1) 
     116    assert_equal developer.name, multi_observer.record.name 
    108117  end 
    109    
     118 
    110119  def test_observing_subclasses 
    111120    multi_observer = MultiObserver.instance 
    112121 
    113122    developer = SpecialDeveloper.find(1) 
    114     assert_equal multi_observer.record.name, developer.name 
     123    assert_equal developer.name, multi_observer.record.name 
     124 
     125    klass = Class.new(Developer) 
     126    assert_equal klass, multi_observer.last_inherited 
     127 
     128    developer = klass.find(1) 
     129    assert_equal developer.name, multi_observer.record.name 
     130  end 
     131 
     132  def test_invalid_observer 
     133    assert_raise(ArgumentError) { Topic.observers = Object.new } 
    115134  end 
    116135end