Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

+ ActiveRecord::Base#destroy! #6629

Merged
merged 1 commit into from

3 participants

@marcandre

save! and variants are super useful when we have some generic exception handler setup, for example with rescue_from, and we expect the operation to succeed.

destroy actions would benefit the same way of a destroy! version in the same way (especially since they so often succeed).

Doesn't it sound great too? destroy!

activerecord/lib/active_record/persistence.rb
@@ -130,6 +135,17 @@ def destroy
freeze
end
+ # Deletes the record in the database and freezes this instance to reflect
+ # that no changes should be made (since they can't be persisted).
+ #
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotSaved. See
+ # ActiveRecord::Callbacks for further details.
+ def destroy!
+ destroy or raise ActiveRecord::RecordNotDestroyed
@josevalim Owner

Can we use ||? It is in Rails Code Guidelines to not use and and or. Thanks!

Sure, done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@carlosantoniodasilva carlosantoniodasilva commented on the diff
activerecord/test/cases/callbacks_test.rb
((5 lines not shown))
assert_not_nil ImmutableDeveloper.find_by_id(1)
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_destroy = true
assert !someone.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }

I think these assertions should go to another test in persistence_test instead, they make more sense there where things like save! are already being tested, wdyt?

Currently, the test for destroy in persistence_test doesn't test failures. This is only done in callback_test. I added the exact same corresponding tests for destroy!.

Moreover, there is no accessible model that cancels destruction in the callbacks; these are inlined in callback_test.

If you want, I can:
1) Move some model out of callback_test
2) Move some destroy testing out of callback_test into persistence_test
3) Move the corresponding destroy! testing.

These behavior seem pretty intertwined to me (as only callbacks can make destroy return false, unless I'm mistaken), so I think that as long as the test is in either file we're ok.

Thanks

I don't think it's necessary to do all this moving, I just thought that'd be reasonable to have destroy! closer to save! tests. About callbacks, one could return false from the destroy method itself without touching the callback chain. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@marcandre

BTW, the commit also supplements the rdoc for destroy, mentioning callbacks and return value of false.

@carlosantoniodasilva

Great, can we have a changelog entry as well please?

@marcandre

Changelog entry added.

activerecord/lib/active_record/persistence.rb
@@ -130,6 +135,17 @@ def destroy
freeze
end
+ # Deletes the record in the database and freezes this instance to reflect
+ # that no changes should be made (since they can't be persisted).
+ #
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotSaved. See

Ops, just noticed the constant name is wrong here, should be ActiveRecord::RecordNotDestroyed, right?

Ah, the pitfalls of copy-paste :-)
Fixed, thanks.

Hehe :), thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@carlosantoniodasilva carlosantoniodasilva merged commit e638c61 into rails:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 6, 2012
  1. @marcandre
This page is out of date. Refresh to see the latest.
View
5 activerecord/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* Added `#destroy!` which acts like `#destroy` but will raise an
+ `ActiveRecord::RecordNotDestroyed` exception instead of returning `false`.
+
+ *Marc-André Lafortune*
+
* Allow blocks for `count` with `ActiveRecord::Relation`, to work similar as
`Array#count`:
View
4 activerecord/lib/active_record/errors.rb
@@ -53,6 +53,10 @@ class RecordNotFound < ActiveRecordError
class RecordNotSaved < ActiveRecordError
end
+ # Raised by ActiveRecord::Base.destroy! when a call to destroy would return false.
+ class RecordNotDestroyed < ActiveRecordError
+ end
+
# Raised when SQL statement cannot be executed by the database (for example, it's often the case for
# MySQL when Ruby driver used is too old).
class StatementInvalid < ActiveRecordError
View
16 activerecord/lib/active_record/persistence.rb
@@ -122,6 +122,11 @@ def delete
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
+ #
+ # There's a series of callbacks associated with <tt>destroy</tt>. If
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
+ # and <tt>destroy</tt> returns +false+. See
+ # ActiveRecord::Callbacks for further details.
def destroy
raise ReadOnlyRecord if readonly?
destroy_associations
@@ -130,6 +135,17 @@ def destroy
freeze
end
+ # Deletes the record in the database and freezes this instance to reflect
+ # that no changes should be made (since they can't be persisted).
+ #
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
+ # ActiveRecord::Callbacks for further details.
+ def destroy!
+ destroy || raise(ActiveRecord::RecordNotDestroyed)
+ end
+
# Returns an instance of the specified +klass+ with the attributes of the
# current record. This is mostly useful in relation to single-table
# inheritance structures where you want a subclass to appear as the
View
2  activerecord/test/cases/callbacks_test.rb
@@ -426,11 +426,13 @@ def test_before_update_returning_false
def test_before_destroy_returning_false
david = ImmutableDeveloper.find(1)
assert !david.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
assert_not_nil ImmutableDeveloper.find_by_id(1)
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_destroy = true
assert !someone.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }

I think these assertions should go to another test in persistence_test instead, they make more sense there where things like save! are already being tested, wdyt?

Currently, the test for destroy in persistence_test doesn't test failures. This is only done in callback_test. I added the exact same corresponding tests for destroy!.

Moreover, there is no accessible model that cancels destruction in the callbacks; these are inlined in callback_test.

If you want, I can:
1) Move some model out of callback_test
2) Move some destroy testing out of callback_test into persistence_test
3) Move the corresponding destroy! testing.

These behavior seem pretty intertwined to me (as only callbacks can make destroy return false, unless I'm mistaken), so I think that as long as the test is in either file we're ok.

Thanks

I don't think it's necessary to do all this moving, I just thought that'd be reasonable to have destroy! closer to save! tests. About callbacks, one could return false from the destroy method itself without touching the callback chain. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
assert !someone.after_destroy_called
end
View
7 activerecord/test/cases/persistence_test.rb
@@ -305,6 +305,13 @@ def test_destroy
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
end
+ def test_destroy!
+ topic = Topic.find(1)
+ assert_equal topic, topic.destroy!, 'topic.destroy! did not return self'
+ assert topic.frozen?, 'topic not frozen after destroy!'
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
+ end
+
def test_record_not_found_exception
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(99999) }
end
Something went wrong with that request. Please try again.