aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG5
-rw-r--r--activerecord/lib/active_record.rb4
-rw-r--r--activerecord/lib/active_record/observer.rb26
-rw-r--r--activerecord/lib/active_record/railtie.rb10
-rw-r--r--activerecord/lib/rails/generators/active_record.rb5
-rwxr-xr-xactiverecord/test/cases/base_test.rb5
-rw-r--r--activerecord/test/cases/callbacks_observers_test.rb37
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/lifecycle_test.rb54
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb3
-rw-r--r--activerecord/test/fixtures/binaries.yml3
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