From d2fefbe908db879745df854787653113a6bcbfac Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 6 Mar 2005 14:11:26 +0000 Subject: [PATCH] Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@853 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/CHANGELOG | 10 ++++++++ activerecord/lib/active_record/base.rb | 32 ++++++++++++++++++++++++-- activerecord/test/base_test.rb | 10 ++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 94183447f7..46afe282cb 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,15 @@ *SVN* +* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation: + + * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the + +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+ + objects that should be inspected to determine which attributes triggered the errors. + * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method. + You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error. + +* Fixed that postgresql adapter would fails when reading bytea fields with null value #771 [rodrigo k] + * Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 [bitsweat] * Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example: diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 1111296306..2725e3c7f5 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -26,6 +26,22 @@ module ActiveRecord #:nodoc: class StaleObjectError < ActiveRecordError #:nodoc: end + class AttributeAssignmentError < ActiveRecordError #:nodoc: + attr_reader :exception, :attribute + def initialize(message, exception, attribute) + @exception = exception + @attribute = attribute + @message = message + end + end + + class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc: + attr_reader :errors + def initialize(errors) + @errors = errors + end + end + # Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain @@ -184,7 +200,11 @@ module ActiveRecord #:nodoc: # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions. # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message. # Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions. - # + # * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the + # +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+ + # objects that should be inspected to determine which attributes triggered the errors. + # * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method. + # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error. # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level). # So it's possible to assign a logger to the class through Base.logger= which will then be used by all # instances in the current object space. @@ -1251,14 +1271,22 @@ module ActiveRecord #:nodoc: # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself. def execute_callstack_for_multiparameter_attributes(callstack) + errors = [] callstack.each do |name, values| klass = (self.class.reflect_on_aggregation(name) || column_for_attribute(name)).klass if values.empty? send(name + "=", nil) else - send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values)) + begin + send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values)) + rescue => ex + errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name) + end end end + unless errors.empty? + raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes" + end end def extract_callstack_for_multiparameter_attributes(pairs) diff --git a/activerecord/test/base_test.rb b/activerecord/test/base_test.rb index 2a704424b1..71641601db 100755 --- a/activerecord/test/base_test.rb +++ b/activerecord/test/base_test.rb @@ -216,6 +216,16 @@ class BasicsTest < Test::Unit::TestCase assert_equal("initialized from attributes", topic.title) end + def test_initialize_with_invalid_attribute + begin + topic = Topic.new({ "title" => "test", + "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"}) + rescue ActiveRecord::MultiparameterAssignmentErrors => ex + assert_equal(1, ex.errors.size) + assert_equal("last_read", ex.errors[0].attribute) + end + end + def test_load topics = Topic.find_all nil, "id" assert_equal(2, topics.size)