Cascade Deleter is a ruby gem designed to delete a set of items with all of their children, grandchildren, grandgrandchildren, i.e. delete items and all of their descending hierarchy.
Why it is necessary? ๐ก
Currently, Rails doesn't have a builtin one-liner way to delete items with all of their descending hierarchy, this type of deletion requires manual and tedious work, since you need to discover which items should be deleted for each descending classes, one by one.
Well, CascadeDeleter
solves this issue perfectly with a oneliner command ๐
CascadeDeleter.new(MyModel.where(my_query)).delete_all
Example ๐งโ๐ซ
As an illustrative example, let's think about the following classes structure:
- Class
Person
has_many books - Class
Book
has_many pages - Class
Page
has_many words - Class
Word
That means: Person
is parent of a list of Book
s, which is parent of a list of Page
s, which is parent of a list of Word
s.
Now, imagine that you want to delete people with id = 1
, id = 2
and id = 3
of the following hierarchy:
Hierarchy
The correct solution would be to to delete it from the leaves to the root, which means deleting the items on this order:
Deletions Order
โ โ โก โ โข โ ๐ฉ
โคท That means...
โ . Delete the words
that belongs to these people
through the word
โ page
โ book
โ people
relationship.
(Word A
, Word B
, Word C
, Word D
, Word E
, Word F
, Word G
, Word H
, Word I
)
โก. Delete the pages
that belongs to these people
through the page
โ book
โ people
relationship.
(Page A
, Page B
, Page C
, Page D
, Page E
, Page F
, Page G
)
โข. Delete the books
that belongs to these people
through the book
โ people
relationship.
(Book 1
, Book 2
, Book 3
, Book 4
)
๐ฉ. Finally deleting the people
(Person 1
, Person 2
, Person 3
)
With the cascade-deleter
gem, these deletions will be done automatically just executing the following oneliner command ๐
CascadeDeleter.new(Person.where(id: [1, 2, 3]).delete_all
# "Person.where(id: [1, 2, 3])" is used for this example, but you can place any ActiveRecord Relation as an argument here!
Installation โ๏ธ
Add cascade-deleter
to your Gemfile.
gem 'cascade-deleter'
Usage ๐
Just require the cascade_deleter
library and use it! (You can test this on rails console
)
Usage โ
Hard Delete of inactive Projects
CascadeDeleter.new(Project.unscoped.where(active: false)).delete_all
Usage โก
Hard Delete of inactive Projects by skipping some classes
CascadeDeleter.new(Project.unscoped.where(active: false)).delete_all(
except: ['Audited::Audit', 'Picture', 'Attachment']
)
Usage โข
Hard Delete of inactive Projects overriding the joins
parameter.
You can override the joins
parameter through the custom_joins
attribute if you want more accurate relationships
in case the joins
is not provided (Usage โ ), the shortest path between each children class and the root class will be chosen for each join
CascadeDeleter.new(Project.unscoped.where(active: false)).delete_all(
custom_joins: {
'Attachment' => {:subproject=>:project}
}
)
โคท That means: When deleting the Attachment
descending class of Project
, the following statement will be executed:
Attachment.joins({:subproject=>:project}).where(projects: { active: false }).delete_all
Usage โฃ
Soft Delete of TO BE DELETED Disciplines
CascadeDeleter.new(Discipline.where(description: '[TO BE DELETED]')).delete_all(
method: :soft
)
โ ๏ธ When using Soft deletion (method: :soft
), be aware that the active
boolean parameter of your database tables will be used, so you need to have the active
boolean parameter in your database tables when using Soft deletion.
t.boolean "active", default: true
Why not use dependent: :delete
/ dependent: :delete_all
instead of CascadeDeleter
? ๐ค
- ๐๐ข๐ฆ๐ฉ๐ฅ๐ข๐๐ข๐ญ๐ฒ
The "dependent" solution not only will require you to add dependent: :delete
/ dependent: :delete_all
on all the models you want to perform the cascade deletion, but it will still raise the Mysql2::Error: Cannot delete or update a parent row
MySQL error while deleting the root items in case you don't have all of your database foreign keys setted up with foreign_key: { on_delete: :cascade }
.
So, for example, if you want to delete 10 Projects that has 50 descending application models, you would need to add dependent: :delete
/ dependent: :delete_all
on the 50 application models as well as executing new migrations changing all of the foreign keys of each one of these 50 tables to foreign_key: { on_delete: :cascade }
.
In a comparison, if you decide to use CascadeDeleter
, you would just need to execute this one-liner command which achieves the same goal:
CascadeDeleter.where(Project.where(id: (1..10))).delete_all
- ๐ ๐ฅ๐๐ฑ๐ข๐๐ข๐ฅ๐ข๐ญ๐ฒ
Another advantage is that you can perform Soft Deletions instead of Hard Deletions on your data, which can be very in handy for systems where you want to deactivate items instead of removing them completely from the database.
CascadeDeleter.where(Project.where(id: (1..10))).delete_all(method: :soft)
- ๐๐๐ซ๐๐จ๐ซ๐ฆ๐๐ง๐๐
Finally, you can also decide to delete a set of root items instead of deleting these root items one-by-one. This can be shown on the above examples, which deletes 10 Projects instead of deleting the projects individually. This fact increases the performance, since a single SQL delete operation is executed for a desired table. This process is also applied on the descending classes.
Contact
*This repository is maintained and developed by Victor Cordeiro Costa. For inquiries, partnerships, or support, don't hesitate to get in touch.