diff options
author | Joshua Peek <josh@joshpeek.com> | 2009-06-10 23:35:34 -0500 |
---|---|---|
committer | Joshua Peek <josh@joshpeek.com> | 2009-06-10 23:36:44 -0500 |
commit | 4d703592001eef1f7e670660cd701e32105bd521 (patch) | |
tree | db36d270b23a4a6e3a01d59e25ad92257d16ef9a | |
parent | 59c83af18b1a9189f82b9c5408cd1bad35a11eb2 (diff) | |
download | rails-4d703592001eef1f7e670660cd701e32105bd521.tar.gz rails-4d703592001eef1f7e670660cd701e32105bd521.tar.bz2 rails-4d703592001eef1f7e670660cd701e32105bd521.zip |
Integrate ActiveModel::Observing into ActiveRecord
-rw-r--r-- | activemodel/lib/active_model.rb | 9 | ||||
-rw-r--r-- | activemodel/lib/active_model/observing.rb | 108 | ||||
-rw-r--r-- | activemodel/test/cases/observing_test.rb | 32 | ||||
-rw-r--r-- | activerecord/lib/active_record.rb | 2 | ||||
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 2 | ||||
-rw-r--r-- | 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 |