diff options
| -rw-r--r-- | activerecord/lib/active_record/persistence.rb | 17 | ||||
| -rw-r--r-- | activerecord/test/cases/persistence_test.rb | 57 | ||||
| -rw-r--r-- | guides/source/active_record_validations_callbacks.textile | 2 | 
3 files changed, 76 insertions, 0 deletions
| diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a23597be28..bd5d8de69c 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -211,6 +211,23 @@ module ActiveRecord        end      end +    # Updates the attributes from the passed-in hash, without calling save. +    # +    # * Validation is skipped. +    # * Callbacks are skipped. +    # * updated_at/updated_on column is not updated if that column is available. +    # +    # Raises an +ActiveRecordError+ when called on new objects, or when at least +    # one of the attributes is marked as readonly. +    def update_columns(attributes) +      raise ActiveRecordError, "can not update on a new record object" unless persisted? +      attributes.each_key {|key| raise ActiveRecordError, "#{key.to_s} is marked as readonly" if self.class.readonly_attributes.include?(key.to_s) } +      attributes.each do |k,v| +        raw_write_attribute(k,v) +      end +      self.class.where(self.class.primary_key => id).update_all(attributes) == 1 +    end +      # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).      # The increment is performed directly on the underlying attribute, no setter is invoked.      # Only makes sense for number-based attributes. Returns +self+. diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index b7b77b24af..77a5458b2a 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -461,6 +461,63 @@ class PersistencesTest < ActiveRecord::TestCase      assert_equal 'super_title', t.title    end +  def test_update_columns +    topic = Topic.find(1) +    topic.update_columns({ "approved" => true, title: "Sebastian Topic" }) +    assert topic.approved? +    assert_equal "Sebastian Topic", topic.title +    topic.reload +    assert topic.approved? +    assert_equal "Sebastian Topic", topic.title +  end + +  def test_update_columns_should_raise_exception_if_new_record +    topic = Topic.new +    assert_raises(ActiveRecord::ActiveRecordError) { topic.update_columns({ approved: false }) } +  end + +  def test_update_columns_should_not_leave_the_object_dirty +    topic = Topic.find(1) +    topic.update_attributes({ "content" => "Have a nice day", :author_name => "Jose" }) + +    topic.reload +    topic.update_columns({ content: "You too", "author_name" => "Sebastian" }) +    assert_equal [], topic.changed + +    topic.reload +    topic.update_columns({ content: "Have a nice day", author_name: "Jose" }) +    assert_equal [], topic.changed +  end + +  def test_update_columns_with_one_readonly_attribute +    minivan = Minivan.find('m1') +    prev_color = minivan.color +    prev_name = minivan.name +    assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_columns({ name: "My old minivan", color: 'black' }) } +    assert_equal prev_color, minivan.color +    assert_equal prev_name, minivan.name + +    minivan.reload +    assert_equal prev_color, minivan.color +    assert_equal prev_name, minivan.name +  end + +  def test_update_columns_should_not_modify_updated_at +    developer = Developer.find(1) +    prev_month = Time.now.prev_month + +    developer.update_column(:updated_at, prev_month) +    assert_equal prev_month, developer.updated_at + +    developer.update_columns({ salary: 80000 }) +    assert_equal prev_month, developer.updated_at +    assert_equal 80000, developer.salary + +    developer.reload +    assert_equal prev_month.to_i, developer.updated_at.to_i +    assert_equal 80000, developer.salary +  end +    def test_update_attributes      topic = Topic.find(1)      assert !topic.approved? diff --git a/guides/source/active_record_validations_callbacks.textile b/guides/source/active_record_validations_callbacks.textile index f49d91fd3c..673894c93a 100644 --- a/guides/source/active_record_validations_callbacks.textile +++ b/guides/source/active_record_validations_callbacks.textile @@ -86,6 +86,7 @@ The following methods skip validations, and will save the object to the database  * +update_all+  * +update_attribute+  * +update_column+ +* +update_columns+  * +update_counters+  Note that +save+ also has the ability to skip validations if passed +:validate => false+ as argument. This technique should be used with caution. @@ -1082,6 +1083,7 @@ Just as with validations, it is also possible to skip callbacks. These methods s  * +toggle+  * +touch+  * +update_column+ +* +update_columns+  * +update_all+  * +update_counters+ | 
