require 'observer' require 'singleton' require 'active_model/core' module ActiveModel module Observing module ClassMethods def observers @observers ||= [] end def observers=(*values) @observers = values.flatten end 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" 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 } end def observed_class_name @observed_class_name ||= if guessed_name = name.scan(/(.*)Observer/)[0] @observed_class_name = guessed_name[0] end 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 observed_class_name.constantize else nil end end end # 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) } 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]) subclass.add_observer(self) end protected def observed_classes @observed_classes ||= [self.class.observed_class] end end end