1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #44141 from drewtempelmeyer/activerecord-update-attributes-exclamation

Add ActiveRecord::Persistence#update_attribute!
This commit is contained in:
Rafael Mendonça França 2022-01-11 17:43:50 -05:00 committed by GitHub
commit 0638d35a78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 1 deletions

View file

@ -1,3 +1,26 @@
* Add `update_attribute!` to `ActiveRecord::Persistence`
Similar to `update_attribute`, but raises `ActiveRecord::RecordNotSaved` when a `before_*` callback throws `:abort`.
```ruby
class Topic < ActiveRecord::Base
before_save :check_title
def check_title
throw(:abort) if title == "abort"
end
end
topic = Topic.create(title: "Test Title")
# #=> #<Topic title: "Test Title">
topic.update_attribute!(:title, "Another Title")
# #=> #<Topic title: "Another Title">
topic.update_attribute!(:title, "abort")
# raises ActiveRecord::RecordNotSaved
```
*Drew Tempelmeyer*
* Avoid loading every record in `ActiveRecord::Relation#pretty_print` * Avoid loading every record in `ActiveRecord::Relation#pretty_print`
```ruby ```ruby

View file

@ -759,6 +759,28 @@ module ActiveRecord
save(validate: false) save(validate: false)
end end
# Updates a single attribute and saves the record.
# This is especially useful for boolean flags on existing records. Also note that
#
# * Validation is skipped.
# * \Callbacks are invoked.
# * updated_at/updated_on column is updated if that column is available.
# * Updates all the attributes that are dirty in this object.
#
# This method raises an ActiveRecord::ActiveRecordError if the
# attribute is marked as readonly.
#
# If any of the <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled
# and #update_attribute! raises ActiveRecord::RecordNotSaved. See
# ActiveRecord::Callbacks for further details.
def update_attribute!(name, value)
name = name.to_s
verify_readonly_attribute(name)
public_send("#{name}=", value)
save!(validate: false)
end
# Updates the attributes of the model from the passed-in hash and saves the # Updates the attributes of the model from the passed-in hash and saves the
# record, all wrapped in a transaction. If the object is invalid, the saving # record, all wrapped in a transaction. If the object is invalid, the saving
# will fail and false will be returned. # will fail and false will be returned.

View file

@ -813,6 +813,69 @@ class PersistenceTest < ActiveRecord::TestCase
assert_not_equal prev_month, developer.updated_at assert_not_equal prev_month, developer.updated_at
end end
def test_update_attribute!
assert_not_predicate Topic.find(1), :approved?
Topic.find(1).update_attribute!("approved", true)
assert_predicate Topic.find(1), :approved?
Topic.find(1).update_attribute!(:approved, false)
assert_not_predicate Topic.find(1), :approved?
Topic.find(1).update_attribute!(:change_approved_before_save, true)
assert_predicate Topic.find(1), :approved?
end
def test_update_attribute_for_readonly_attribute!
minivan = Minivan.find("m1")
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute!(:color, "black") }
end
def test_update_attribute_with_one_updated!
t = Topic.first
t.update_attribute!(:title, "super_title")
assert_equal "super_title", t.title
assert_not t.changed?, "topic should not have changed"
assert_not t.title_changed?, "title should not have changed"
assert_nil t.title_change, "title change should be nil"
t.reload
assert_equal "super_title", t.title
end
def test_update_attribute_for_updated_at_on!
developer = Developer.find(1)
prev_month = Time.now.prev_month.change(usec: 0)
developer.update_attribute!(:updated_at, prev_month)
assert_equal prev_month, developer.updated_at
developer.update_attribute!(:salary, 80001)
assert_not_equal prev_month, developer.updated_at
developer.reload
assert_not_equal prev_month, developer.updated_at
end
def test_update_attribute_for_aborted_callback!
klass = Class.new(Topic) do
def self.name; "Topic"; end
before_update :throw_abort
def throw_abort
throw(:abort)
end
end
t = klass.create(title: "New Topic", author_name: "Not David")
assert_raises(ActiveRecord::RecordNotSaved) { t.update_attribute!(:title, "super_title") }
t_reloaded = Topic.find(t.id)
assert_equal "New Topic", t_reloaded.title
end
def test_update_column def test_update_column
topic = Topic.find(1) topic = Topic.find(1)
topic.update_column("approved", true) topic.update_column("approved", true)