diff options
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/CHANGELOG | 5 | ||||
-rw-r--r-- | activerecord/lib/active_record.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/observer.rb | 26 | ||||
-rw-r--r-- | activerecord/lib/active_record/railtie.rb | 10 | ||||
-rw-r--r-- | activerecord/lib/rails/generators/active_record.rb | 5 | ||||
-rwxr-xr-x | activerecord/test/cases/base_test.rb | 5 | ||||
-rw-r--r-- | activerecord/test/cases/callbacks_observers_test.rb | 37 | ||||
-rw-r--r-- | activerecord/test/cases/fixtures_test.rb | 2 | ||||
-rw-r--r-- | activerecord/test/cases/lifecycle_test.rb | 54 | ||||
-rw-r--r-- | activerecord/test/cases/xml_serialization_test.rb | 3 | ||||
-rw-r--r-- | activerecord/test/fixtures/binaries.yml | 3 |
11 files changed, 99 insertions, 55 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 8ecef6574f..c0c4df5035 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,3 +1,8 @@ +*Rails 3.0.0 [beta 4/release candidate] (unreleased)* + +* Observers can prevent records from saving by returning false, just like before_save and friends. #4087 [Mislav Marohnić] + + *Rails 3.0.0 [beta 3] (April 13th, 2010)* * Add Relation extensions. [Pratik Naik] diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 8a1aa50e24..6a6485f35e 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -29,7 +29,9 @@ activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) require 'active_support' +require 'active_support/i18n' require 'active_model' +require 'arel' module ActiveRecord extend ActiveSupport::Autoload @@ -117,4 +119,4 @@ ActiveSupport.on_load(:active_record) do Arel::Table.engine = Arel::Sql::Engine.new(self) end -I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
\ No newline at end of file +I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 0fbb1f0261..ed0f039597 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -96,7 +96,8 @@ module ActiveRecord end def self.method_added(method) - self.observed_methods += [method] if ActiveRecord::Callbacks::CALLBACKS.include?(method.to_sym) + method = method.to_sym + observed_methods << method if ActiveRecord::Callbacks::CALLBACKS.include?(method) end protected @@ -104,16 +105,27 @@ module ActiveRecord observed_classes.sum([]) { |klass| klass.send(:subclasses) } end + def observe_callbacks? + self.class.observed_methods.any? + end + def add_observer!(klass) super + define_callbacks klass if observe_callbacks? + end + + def define_callbacks(klass) + existing_methods = klass.instance_methods.map(&:to_sym) + observer = self + observer_name = observer.class.name.underscore.gsub('/', '__') - # Check if a notifier callback was already added to the given class. If - # it was not, add it. self.class.observed_methods.each do |method| - callback = :"_notify_observers_for_#{method}" - if (klass.instance_methods & [callback, callback.to_s]).empty? - klass.class_eval "def #{callback}; notify_observers(:#{method}); end" - klass.send(method, callback) + callback = :"_notify_#{observer_name}_for_#{method}" + unless existing_methods.include? callback + klass.send(:define_method, callback) do # def _notify_user_observer_for_before_save + observer.update(method, self) # observer.update(:before_save, self) + end # end + klass.send(method, callback) # before_save :_notify_user_observer_for_before_save end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 04c4a9c153..878a4dac09 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -70,9 +70,7 @@ module ActiveRecord end end - initializer "active_record.load_observers" do - ActiveSupport.on_load(:active_record) { instantiate_observers } - + initializer "active_record.add_observer_hook", :after=>"active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do ActiveRecord::Base.instantiate_observers @@ -80,6 +78,12 @@ module ActiveRecord end end + initializer "active_record.instantiate_observers", :after=>"active_record.initialize_database" do + ActiveSupport.on_load(:active_record) do + instantiate_observers + end + end + initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app| ActiveSupport.on_load(:active_record) do unless app.config.cache_classes diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb index 1ca838b4f2..d2b1e86857 100644 --- a/activerecord/lib/rails/generators/active_record.rb +++ b/activerecord/lib/rails/generators/active_record.rb @@ -19,10 +19,11 @@ module ActiveRecord # Implement the required interface for Rails::Generators::Migration. # def self.next_migration_number(dirname) #:nodoc: + next_migration_number = current_migration_number(dirname) + 1 if ActiveRecord::Base.timestamped_migrations - Time.now.utc.strftime("%Y%m%d%H%M%S") + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max else - "%.3d" % (current_migration_number(dirname) + 1) + "%.3d" % next_migration_number end end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 1c3655b587..2f4243a6aa 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -2054,7 +2054,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_base_subclasses_is_public_method - assert ActiveRecord::Base.public_methods.include?("subclasses") + assert ActiveRecord::Base.public_methods.map(&:to_sym).include?(:subclasses) end def test_find_on_abstract_base_class_doesnt_use_type_condition @@ -2095,7 +2095,8 @@ class BasicsTest < ActiveRecord::TestCase assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text assert_equal "datetime" , xml.elements["//written-on"].attributes['type'] - assert_equal "--- Have a nice day\n" , xml.elements["//content"].text + assert_match(/^--- Have a nice day\n/ , xml.elements["//content"].text) + assert_equal 'Have a nice day' , YAML.load(xml.elements["//content"].text) assert_equal "yaml" , xml.elements["//content"].attributes['type'] assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text diff --git a/activerecord/test/cases/callbacks_observers_test.rb b/activerecord/test/cases/callbacks_observers_test.rb deleted file mode 100644 index 52ce384844..0000000000 --- a/activerecord/test/cases/callbacks_observers_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require "cases/helper" - -class Comment < ActiveRecord::Base - attr_accessor :callers - - before_validation :record_callers - - after_validation do - record_callers - end - - def record_callers - callers << self.class if callers - end -end - -class CommentObserver < ActiveRecord::Observer - attr_accessor :callers - - def after_validation(model) - callers << self.class if callers - end -end - -class CallbacksObserversTest < ActiveRecord::TestCase - def test_model_callbacks_fire_before_observers_are_notified - callers = [] - - comment = Comment.new - comment.callers = callers - - CommentObserver.instance.callers = callers - - comment.valid? - assert_equal [Comment, Comment, CommentObserver], callers, "model callbacks did not fire before observers were notified" - end -end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index e78b522b65..d24283fe4e 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -26,7 +26,7 @@ class FixturesTest < ActiveRecord::TestCase FIXTURES = %w( accounts binaries companies customers developers developers_projects entrants movies projects subscribers topics tasks ) - MATCH_ATTRIBUTE_NAME = /[a-zA-Z][-_\w]*/ + MATCH_ATTRIBUTE_NAME = /[a-zA-Z][-\w]*/ def test_clean_fixtures FIXTURES.each do |name| diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb index fcad3e90d3..233338498f 100644 --- a/activerecord/test/cases/lifecycle_test.rb +++ b/activerecord/test/cases/lifecycle_test.rb @@ -3,9 +3,18 @@ require 'models/topic' require 'models/developer' require 'models/reply' require 'models/minimalistic' +require 'models/comment' class SpecialDeveloper < Developer; end +class SalaryChecker < ActiveRecord::Observer + observe :special_developer + + def before_save(developer) + return developer.salary > 80000 + end +end + class TopicaAuditor < ActiveRecord::Observer observe :topic @@ -57,6 +66,28 @@ class MultiObserver < ActiveRecord::Observer 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 LifecycleTest < ActiveRecord::TestCase fixtures :topics, :developers, :minimalistics @@ -125,4 +156,27 @@ class LifecycleTest < ActiveRecord::TestCase 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 end diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index 2849ff11b7..b1c75ec8cd 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -4,6 +4,7 @@ require 'models/post' require 'models/author' require 'models/tagging' require 'models/comment' +require 'models/company_in_module' class XmlSerializationTest < ActiveRecord::TestCase def test_should_serialize_default_root @@ -79,7 +80,7 @@ class DefaultXmlSerializationTest < ActiveRecord::TestCase end def test_should_serialize_yaml - assert_match %r{<preferences type=\"yaml\">--- \n:gem: ruby\n</preferences>}, @xml + assert_match %r{<preferences type=\"yaml\">---\s?\n:gem: ruby\n</preferences>}, @xml end end diff --git a/activerecord/test/fixtures/binaries.yml b/activerecord/test/fixtures/binaries.yml index d150c5708e..ec8f2facdc 100644 --- a/activerecord/test/fixtures/binaries.yml +++ b/activerecord/test/fixtures/binaries.yml @@ -1,6 +1,7 @@ flowers: id: 1 - data: !binary | /9j/4AAQSkZJRgABAQEASABIAAD/2wBDAA0JCQoKCg4LCw4UDQsNFBcRDg4R + data: !binary |- + /9j/4AAQSkZJRgABAQEASABIAAD/2wBDAA0JCQoKCg4LCw4UDQsNFBcRDg4R FxsVFRUVFRsbFRcXFxcVGxoeICEgHhonJyoqJyc1NTU1NTY2NjY2NjY2Njb/ 2wBDAQ4NDRERERcRERcXExQTFx0ZGhoZHSYdHR4dHSYsJCAgICAkLCgrJiYm KygvLywsLy82NjY2NjY2NjY2NjY2Njb/wAARCACvAIMDAREAAhEBAxEB/8QA |