Similar to the
SaveOperation, Avram comes with a
DeleteOperation that’s generated with each model.
This allows you to write more complex logic around deleteing records. (e.g. delete confirmations, etc…)
If you just want to delete a record without any validations or callbacks, the simplest way is to use the generated
For example, if we have a
Server model, Lucky will generate a
Server::DeleteOperation that you can use:
server = ServerQuery.find(123) Server::DeleteOperation.delete!(server)
If the record fails to be deleted, an
Avram::InvalidOperationError will be raised.
You can customize DeleteOperations with callbacks and validations. These
classes go in your
src/operations/ directory, and will inherit from
# src/operations/delete_server.cr class DeleteServer < Server::DeleteOperation end
The interface should feel pretty familiar. The object being deleted is passed in to the
delete method, and a block will
return the operation instance, and the object being deleted.
# src/actions/servers/delete.cr class Servers::Delete < BrowserAction delete "/servers/:server_id" do server = ServerQuery.find(server_id) DeleteServer.delete(server) do |operation, deleted_server| if operation.deleted? redirect to: Servers::Index else flash.failure = "Could not delete" html Servers::EditPage, server: deleted_server end end end end
You can also pass in params or named args for use with attributes, or
DeleteServer.delete(server, params, secret_codes: [23_u16, 94_u16]) do |operation, deleted_server| if operation.deleted? redirect to: Servers::Index else flash.failure = "Could not delete" html Servers::EditPage, server: deleted_server end end
You can also use the
delete! method if you don’t need validations and expect deletes to work every time:
This is helpful when your operation only has callbacks or needs and is expected to work every time.
DeleteOperationCallbacks and Validations
DeleteOperations come with
after_delete callbacks that allow you to either validate
some code before performing the delete, or perform some action after deleteing. (i.e. Send a “Goodbye” email, etc…)
Along with the callbacks, you also have access to
needs, and all of the columns related to a model.
You even have
file_attribute for those times you need to use biometric scans to authorize deleting a record!
# src/operations/delete_server.cr class DeleteServer < Server::DeleteOperation attribute confirmation : String before_delete do validate_required confirmation # `record` is the object to be deleted if confirmation.value != record.server_name confirmation.add_error("Confirmation must match the server name") end end end
# src/operations/delete_server.cr class DeleteServer < Server::DeleteOperation needs secret_codes : Array(UInt16) after_delete do |deleted_server| decrypted_server_data = DecryptServer.new(deleted_server, with: secret_codes) DecryptedServerDataEmail.new(decrypted_server_data).deliver end end
Currently bulk deletes with DeleteOperation are not supported.
If you need to bulk delete a group of records based on a where query, you can use
delete at the end of your query.
This returns the number of records deleted.
# DELETE FROM users WHERE banned_at IS NOT NULL UserQuery.new.banned_at.is_not_nil.delete
A “soft delete” is when you want to hide a record as if it were deleted, but you want to keep the actual record in your database. This allows you to restore the record without losing any previous data or associations.
Avram comes with some built-in modules to help make working with soft deleted records a lot easier. Let’s add it
to an existing
soft_deleted_at : Time?column to the table that needs soft deletes.
# Run this in your terminal lucky gen.migration AddSoftDeleteToArticles
def migrate alter table_for(Article) do add soft_deleted_at : Time?, index: true end end
class Article < BaseModel # Include this module to add methods for # soft deleting and restoring include Avram::SoftDelete::Model table do # Add the new column to your model column soft_deleted_at : Time? end end
class ArticleQuery < Article::BaseQuery # Include this module to add methods for # querying and soft deleting records include Avram::SoftDelete::Query end
Once a model includes the
Avram::SoftDelete::Model, the associated DeleteOperation will handle the soft delete for you.
# src/operations/delete_article.cr class DeleteArticle < Article::DeleteOperation end
and in your action
# src/actions/articles/delete.cr class Articles::Delete < BrowserAction delete "/articles/:article_id" do article = ArticleQuery.find(article_id) deleted_article = DeleteArticle.delete!(article) # This returns `true` deleted_article.soft_deleted? redirect to: Articles::Index end end
Currently bulk soft deletes with DeleteOperation are not supported.
You can bulk update a group of records as soft deleted with the
soft_delete method on your Query object.
articles_to_delete = ArticleQuery.new.created_at.gt(3.years.ago) # Marks the articles created over 3 years ago as soft deleted articles_to_delete.soft_delete
If you need to restore a soft deleted record, you can use the
restore method on the model instance.
# Set the `soft_deleted_at` back to `nil` article.restore
The same as we can bulk soft delete records, we can also bulk update to restore them with
restore method on your Query object.
articles_to_restore = ArticleQuery.new.published_at.lt(1.week.ago) # Restore recently published articles articles_to_restore.restore
# Return all articles that are not soft deleted ArticleQuery.new.only_kept # Return all articles that are soft deleted ArticleQuery.new.only_soft_deleted
If you want to filter out soft deleted records by default, it’s really easy to do.
Just add the
only_kept method as the default query in the
class ArticleQuery < Article::BaseQuery include Avram::SoftDelete::Query # All queries will scope to only_kept def initialize defaults &.only_kept end end
# Return all articles that are not soft deleted ArticleQuery.new
Even with your default scope, you can still return soft deleted records when you need.
# Return all articles, both `kept` and soft deleted ArticleQuery.new.with_soft_deleted
If you need to delete every record in the entire table, you can use
TRUNCATE TABLE users
truncatemethod may raise an error similar to the following:
Error message cannot truncate a table referenced in a foreign key constraint.
If that’s the case, call the same method with the
cascadeoption set to
This will automatically delete or update matching records in a child table where a foreign key relationship is in place.
You can also truncate your entire database by calling
truncate on your database class.
This method is great for tests; horrible for production. Also note this method is not chainable.