diff options
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/lib/active_record.rb | 1 | ||||
-rw-r--r-- | activerecord/lib/active_record/base.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/observer.rb | 126 | ||||
-rw-r--r-- | activerecord/lib/active_record/persistence.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/railtie.rb | 22 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 9 | ||||
-rw-r--r-- | activerecord/lib/rails/generators/active_record/observer/observer_generator.rb | 15 | ||||
-rw-r--r-- | activerecord/lib/rails/generators/active_record/observer/templates/observer.rb | 4 | ||||
-rw-r--r-- | activerecord/test/cases/dirty_test.rb | 2 | ||||
-rw-r--r-- | activerecord/test/cases/lifecycle_test.rb | 256 | ||||
-rw-r--r-- | activerecord/test/cases/transaction_callbacks_test.rb | 81 |
11 files changed, 17 insertions, 503 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 45122539f1..822da84d19 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -45,7 +45,6 @@ module ActiveRecord autoload :Migrator, 'active_record/migration' autoload :ModelSchema autoload :NestedAttributes - autoload :Observer autoload :Persistence autoload :QueryCache autoload :Querying diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 5eacb8f143..965fe3f33a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -320,7 +320,6 @@ module ActiveRecord #:nodoc: # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all # instances in the current object space. class Base - extend ActiveModel::Observing::ClassMethods extend ActiveModel::Naming extend ActiveSupport::Benchmarkable @@ -348,7 +347,6 @@ module ActiveRecord #:nodoc: include Locking::Pessimistic include AttributeMethods include Callbacks - include ActiveModel::Observing include Timestamp include Associations include ActiveModel::SecurePassword diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb deleted file mode 100644 index 6b2f6f98a5..0000000000 --- a/activerecord/lib/active_record/observer.rb +++ /dev/null @@ -1,126 +0,0 @@ - -module ActiveRecord - # = Active Record Observer - # - # Observer classes respond to life cycle 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 - # functionality that doesn't pertain to the core responsibility of the - # class. Example: - # - # class CommentObserver < ActiveRecord::Observer - # def after_save(comment) - # Notifications.comment("admin@do.com", "New comment was posted", comment).deliver - # end - # end - # - # This Observer sends an email when a Comment#save is finished. - # - # class ContactObserver < ActiveRecord::Observer - # def after_create(contact) - # contact.logger.info('New contact added!') - # end - # - # def after_destroy(contact) - # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!") - # end - # end - # - # This Observer uses logger to log when specific callbacks are triggered. - # - # == Observing a class that can't be inferred - # - # Observers will by default be mapped to the class with which they share a name. So CommentObserver will - # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer - # differently than the class you're interested in observing, you can use the Observer.observe class method which takes - # either the concrete class (Product) or a symbol for that class (:product): - # - # class AuditObserver < ActiveRecord::Observer - # observe :account - # - # def after_update(account) - # AuditTrail.new(account, "UPDATED") - # end - # end - # - # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments: - # - # class AuditObserver < ActiveRecord::Observer - # observe :account, :balance - # - # def after_update(record) - # AuditTrail.new(record, "UPDATED") - # end - # end - # - # The AuditObserver will now act on both updates to Account and Balance by treating them both as records. - # - # == Available callback methods - # - # The observer can implement callback methods for each of the methods described in the Callbacks module. - # - # == Storing Observers in Rails - # - # If you're using Active Record within Rails, observer classes are usually stored in app/models with the - # naming convention of app/models/audit_observer.rb. - # - # == Configuration - # - # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration - # setting in your <tt>config/application.rb</tt> file. - # - # config.active_record.observers = :comment_observer, :signup_observer - # - # Observers will not be invoked unless you define these in your application configuration. - # - # If you are using Active Record outside Rails, activate the observers explicitly in a configuration or - # environment file: - # - # ActiveRecord::Base.add_observer CommentObserver.instance - # ActiveRecord::Base.add_observer SignupObserver.instance - # - # == Loading - # - # Observers register themselves in the model class they observe, since it is the class that - # notifies them of events when they occur. As a side-effect, when an observer is loaded its - # corresponding model class is loaded. - # - # Up to (and including) Rails 2.0.2 observers were instantiated between plugins and - # application initializers. Now observers are loaded after application initializers, - # so observed models can make use of extensions. - # - # If by any chance you are using observed models in the initialization you can still - # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are - # singletons and that call instantiates and registers them. - # - class Observer < ActiveModel::Observer - - protected - - def observed_classes - klasses = super - klasses + klasses.map { |klass| klass.descendants }.flatten - end - - def add_observer!(klass) - super - define_callbacks klass - end - - def define_callbacks(klass) - observer = self - observer_name = observer.class.name.underscore.gsub('/', '__') - - ActiveRecord::Callbacks::CALLBACKS.each do |callback| - next unless respond_to?(callback) - callback_meth = :"_notify_#{observer_name}_for_#{callback}" - unless klass.respond_to?(callback_meth) - klass.send(:define_method, callback_meth) do |&block| - observer.update(callback, self, &block) - end - klass.send(callback, callback_meth) - end - end - end - end -end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index eed49e17b1..81e56a3e2e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -102,7 +102,7 @@ module ActiveRecord # record's primary key, and no callbacks are executed. # # To enforce the object's +before_destroy+ and +after_destroy+ - # callbacks, Observer methods, or any <tt>:dependent</tt> association + # callbacks or any <tt>:dependent</tt> association # options, use <tt>#destroy</tt>. def delete self.class.delete(id) if persisted? diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index a73473f59c..c3f13b2dac 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -115,6 +115,18 @@ module ActiveRecord See http://edgeguides.rubyonrails.org/security.html#mass-assignment for more information EOF end + + unless app.config.active_record.delete(:observers).nil? + ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, [] + Active Record Observers has been extracted out of Rails into a gem. + Please use callbaks or add `rails-observers` to your Gemfile to use observers. + + To disable this message remove the `observers` option from your + `config/application.rb` or from your initializers. + + See http://edgeguides.rubyonrails.org/4_0_release_notes.html for more information + EOF + end ensure ActiveSupport::Deprecation.behavior = old_behavior end @@ -168,15 +180,5 @@ module ActiveRecord path = app.paths["db"].first config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"] end - - config.after_initialize do |app| - ActiveSupport.on_load(:active_record) do - instantiate_observers - - ActionDispatch::Reloader.to_prepare do - ActiveRecord::Base.instantiate_observers - end - end - end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f0f170b684..0df895eb67 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -315,11 +315,9 @@ module ActiveRecord # Destroys the records matching +conditions+ by instantiating each # record and calling its +destroy+ method. Each object's callbacks are - # executed (including <tt>:dependent</tt> association options and - # +before_destroy+/+after_destroy+ Observer methods). Returns the + # executed (including <tt>:dependent</tt> association options). Returns the # collection of objects that were destroyed; each will be frozen, to - # reflect that no changes should be made (since they can't be - # persisted). + # reflect that no changes should be made (since they can't be persisted). # # Note: Instantiation, callback execution, and deletion of each # record can be time consuming when you're removing many records at @@ -419,8 +417,7 @@ module ActiveRecord # Deletes the row with a primary key matching the +id+ argument, using a # SQL +DELETE+ statement, and returns the number of rows deleted. Active # Record objects are not instantiated, so the object's callbacks are not - # executed, including any <tt>:dependent</tt> association options or - # Observer methods. + # executed, including any <tt>:dependent</tt> association options. # # You can delete multiple rows at once by passing an Array of <tt>id</tt>s. # diff --git a/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb b/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb deleted file mode 100644 index e7445d03a2..0000000000 --- a/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'rails/generators/active_record' - -module ActiveRecord - module Generators # :nodoc: - class ObserverGenerator < Base # :nodoc: - check_class_collision :suffix => "Observer" - - def create_observer_file - template 'observer.rb', File.join('app/models', class_path, "#{file_name}_observer.rb") - end - - hook_for :test_framework - end - end -end diff --git a/activerecord/lib/rails/generators/active_record/observer/templates/observer.rb b/activerecord/lib/rails/generators/active_record/observer/templates/observer.rb deleted file mode 100644 index eaa256a9bd..0000000000 --- a/activerecord/lib/rails/generators/active_record/observer/templates/observer.rb +++ /dev/null @@ -1,4 +0,0 @@ -<% module_namespacing do -%> -class <%= class_name %>Observer < ActiveRecord::Observer -end -<% end -%> diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index d3cbd839f6..55ee066cda 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -12,7 +12,7 @@ class Pirate # Just reopening it, not defining it after_update :check_changes private - # after_save/update in sweepers, observers, and the model itself + # after_save/update and the model itself # can end up checking dirty status and acting on the results def check_changes if self.changed? diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb deleted file mode 100644 index 0b78f2e46b..0000000000 --- a/activerecord/test/cases/lifecycle_test.rb +++ /dev/null @@ -1,256 +0,0 @@ -require 'cases/helper' -require 'models/topic' -require 'models/developer' -require 'models/reply' -require 'models/minimalistic' -require 'models/comment' - -class SpecialDeveloper < Developer; end - -class DeveloperObserver < ActiveRecord::Observer - def calls - @calls ||= [] - end - - def before_save(developer) - calls << developer - end -end - -class SalaryChecker < ActiveRecord::Observer - observe :special_developer - attr_accessor :last_saved - - def before_save(developer) - return developer.salary > 80000 - end - - module Implementation - def after_save(developer) - self.last_saved = developer - end - end - include Implementation - -end - -class TopicaAuditor < ActiveRecord::Observer - observe :topic - - attr_reader :topic - - def after_find(topic) - @topic = topic - end -end - -class TopicObserver < ActiveRecord::Observer - attr_reader :topic - - def after_find(topic) - @topic = topic - end - - # Create an after_save callback, so a notify_observer hook is created - # on :topic. - def after_save(nothing) - end -end - -class MinimalisticObserver < ActiveRecord::Observer - attr_reader :minimalistic - - def after_find(minimalistic) - @minimalistic = minimalistic - end -end - -class MultiObserver < ActiveRecord::Observer - attr_reader :record - - def self.observed_class() [ Topic, Developer ] end - - cattr_reader :last_inherited - @@last_inherited = nil - - def observed_class_inherited_with_testing(subclass) - observed_class_inherited_without_testing(subclass) - @@last_inherited = subclass - end - - alias_method_chain :observed_class_inherited, :testing - - def after_find(record) - @record = record - end -end - -class ValidatedComment < Comment - attr_accessor :callers - - before_validation :record_callers - - after_validation do - record_callers - end - - def record_callers - callers << self.class if callers - end -end - -class ValidatedCommentObserver < ActiveRecord::Observer - attr_accessor :callers - - def after_validation(model) - callers << self.class if callers - end -end - - -class AroundTopic < Topic -end - -class AroundTopicObserver < ActiveRecord::Observer - observe :around_topic - def topic_ids - @topic_ids ||= [] - end - - def around_save(topic) - topic_ids << topic.id - yield(topic) - topic_ids << topic.id - end -end - -class LifecycleTest < ActiveRecord::TestCase - fixtures :topics, :developers, :minimalistics - - def test_before_destroy - topic = Topic.find(1) - assert_difference 'Topic.count', -(1 + topic.replies.size) do - topic.destroy - end - end - - def test_auto_observer - topic_observer = TopicaAuditor.instance - assert_nil TopicaAuditor.observed_class - assert_equal [Topic], TopicaAuditor.observed_classes.to_a - - topic = Topic.find(1) - assert_equal topic.title, topic_observer.topic.title - end - - def test_inferred_auto_observer - topic_observer = TopicObserver.instance - assert_equal Topic, TopicObserver.observed_class - - topic = Topic.find(1) - assert_equal topic.title, topic_observer.topic.title - end - - def test_observing_two_classes - multi_observer = MultiObserver.instance - - topic = Topic.find(1) - assert_equal topic.title, multi_observer.record.title - - developer = Developer.find(1) - assert_equal developer.name, multi_observer.record.name - end - - def test_observing_subclasses - multi_observer = MultiObserver.instance - - developer = SpecialDeveloper.find(1) - assert_equal developer.name, multi_observer.record.name - - klass = Class.new(Developer) - assert_equal klass, multi_observer.last_inherited - - developer = klass.find(1) - assert_equal developer.name, multi_observer.record.name - end - - def test_after_find_can_be_observed_when_its_not_defined_on_the_model - observer = MinimalisticObserver.instance - assert_equal Minimalistic, MinimalisticObserver.observed_class - - minimalistic = Minimalistic.find(1) - assert_equal minimalistic, observer.minimalistic - end - - def test_after_find_can_be_observed_when_its_defined_on_the_model - observer = TopicObserver.instance - assert_equal Topic, TopicObserver.observed_class - - topic = Topic.find(1) - assert_equal topic, observer.topic - end - - def test_invalid_observer - assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers } - end - - test "model callbacks fire before observers are notified" do - callers = [] - - comment = ValidatedComment.new - comment.callers = ValidatedCommentObserver.instance.callers = callers - - comment.valid? - assert_equal [ValidatedComment, ValidatedComment, ValidatedCommentObserver], callers, - "model callbacks did not fire before observers were notified" - end - - test "able to save developer" do - SalaryChecker.instance # activate - developer = SpecialDeveloper.new :name => 'Roger', :salary => 100000 - assert developer.save, "developer with normal salary failed to save" - end - - test "unable to save developer with low salary" do - SalaryChecker.instance # activate - developer = SpecialDeveloper.new :name => 'Rookie', :salary => 50000 - assert !developer.save, "allowed to save a developer with too low salary" - end - - test "able to call methods defined with included module" do # https://rails.lighthouseapp.com/projects/8994/tickets/6065-activerecordobserver-is-not-aware-of-method-added-by-including-modules - SalaryChecker.instance # activate - developer = SpecialDeveloper.create! :name => 'Roger', :salary => 100000 - assert_equal developer, SalaryChecker.instance.last_saved - end - - test "around filter from observer should accept block" do - observer = AroundTopicObserver.instance - topic = AroundTopic.new - topic.save - assert_nil observer.topic_ids.first - assert_not_nil observer.topic_ids.last - end - - test "able to disable observers" do - observer = DeveloperObserver.instance # activate - observer.calls.clear - - ActiveRecord::Base.observers.disable DeveloperObserver do - Developer.create! :name => 'Ancestor', :salary => 100000 - SpecialDeveloper.create! :name => 'Descendent', :salary => 100000 - end - - assert_equal [], observer.calls - end - - def test_observer_is_called_once - observer = DeveloperObserver.instance # activate - observer.calls.clear - - developer = Developer.create! :name => 'Ancestor', :salary => 100000 - special_developer = SpecialDeveloper.create! :name => 'Descendent', :salary => 100000 - - assert_equal [developer, special_developer], observer.calls - end - -end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 961ba8d9ba..2ddc449c12 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -247,87 +247,6 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end -class TransactionObserverCallbacksTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false - fixtures :topics - - class TopicWithObserverAttached < ActiveRecord::Base - self.table_name = :topics - def history - @history ||= [] - end - end - - class TopicWithObserverAttachedObserver < ActiveRecord::Observer - def after_commit(record) - record.history.push "after_commit" - end - - def after_rollback(record) - record.history.push "after_rollback" - end - end - - def test_after_commit_called - assert TopicWithObserverAttachedObserver.instance, 'should have observer' - - topic = TopicWithObserverAttached.new - topic.save! - - assert_equal %w{ after_commit }, topic.history - end - - def test_after_rollback_called - assert TopicWithObserverAttachedObserver.instance, 'should have observer' - - topic = TopicWithObserverAttached.new - - Topic.transaction do - topic.save! - raise ActiveRecord::Rollback - end - - assert topic.id.nil? - assert !topic.persisted? - assert_equal %w{ after_rollback }, topic.history - end - - class TopicWithManualRollbackObserverAttached < ActiveRecord::Base - self.table_name = :topics - def history - @history ||= [] - end - end - - class TopicWithManualRollbackObserverAttachedObserver < ActiveRecord::Observer - def after_save(record) - record.history.push "after_save" - raise ActiveRecord::Rollback - end - end - - def test_after_save_called_with_manual_rollback - assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer' - - topic = TopicWithManualRollbackObserverAttached.new - - assert !topic.save - assert_equal nil, topic.id - assert !topic.persisted? - assert_equal %w{ after_save }, topic.history - end - def test_after_save_called_with_manual_rollback_bang - assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer' - - topic = TopicWithManualRollbackObserverAttached.new - - topic.save! - assert_equal nil, topic.id - assert !topic.persisted? - assert_equal %w{ after_save }, topic.history - end -end - class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase self.use_transactional_fixtures = false |