add basic support for deleting account #683
Reference in New Issue
Block a user
Delete Branch "account-deletion"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
fixes #675
/claim #675
DEMO 1 - admin is not allowed to delete account until there are no members
https://github.com/maybe-finance/maybe/assets/119384208/3cf8f17c-460e-49b1-a0f8-030ecc297d28
DEMO 2 - admin account deletion after all members are deleted
https://github.com/maybe-finance/maybe/assets/119384208/86202816-9012-472e-bd7e-941cd63244b5
This draft PR demonstrates a naive implementation of user account deletion that I'll build on over the next few hours.
This implementation deletes the user and attempts to delete other related data in the same request "cycle".
The problem identified while deleting the demo account is that the delete operation can really take a long time depending on the number of accounts associated with the user's family. I have thus decided to do the cleanup in a background job.
UI Requirements
BACKEND Requirements
@@ -17,10 +17,34 @@ class Settings::ProfilesController < ApplicationControllerendendthe primary controller that handles deletion of the user account
It simply deletes the user account (which is the most basic requirement)
and schedules a background job to do required cleanup
Hack really to allow us too add a reject button down there
@@ -25,1 +29,4 @@}if (reject) {const className = document.getElementById("turbo-confirm-reject").className.replace("hidden", "")Allows us to customize the accept button
@@ -0,0 +1,7 @@class DeleteUserJob < ApplicationJobthis deletes accounts and related data
@@ -0,0 +1,7 @@class DeleteUserJob < ApplicationJobThis is my addition thus far.
I figured since family_id is required on all user models,
any existing user with a non-existent family is "invalid"?
@@ -4,6 +4,15 @@ family_admin:last_name: Dylanemail: bob@bobdylan.compassword_digest: <%= BCrypt::Password.create('password') %>role: adminI just wonder why whoever created this test fixture never bothered to specify the admin role?
@@ -0,0 +1,52 @@require "test_helper"We could have more edge cases to test but these are it for now.
I like the test cases here and overall strategy. Just left a few suggestions on how we could simplify some of the logic.
@jemiluv8 also, I've updated the issue requirements to make the deletion logic simpler as I realize from the implementation that the previous requirements may have been trying to achieve too much and added unnecessary complexity:
While I think you're right that we should be doing this in a background job, I think a potentially more stable strategy would be something like this:
And then on login, we could check
marked_for_deletionand fail early if it is markedtrue.This would allow us to work directly with the
Userrecord all in one spot and avoid some of the error handling here.@@ -20,9 +20,19 @@ Turbo.setConfirmMethod((message) => {document.getElementById("turbo-confirm-body").innerHTML = body;I think we could probably move this to the
_confirm_modalpartial and then toggle classes as needed here.@@ -0,0 +1,7 @@class DeleteUserJob < ApplicationJobWhile I think my comment above (passing in the user instance) will simplify all of this logic a bit, we should move a lot of this method into the models themselves. As an example:
This should likely be a scope or method on
Familyso that we can call:These are not necessary since we've got an enum. You should be able to already call
User.first.member?andUser.first.admin?.FYI - https://github.com/maybe-finance/maybe/issues/675#issuecomment-2081051730
@@ -0,0 +1,7 @@class DeleteUserJob < ApplicationJobFYI - https://github.com/maybe-finance/maybe/issues/675#issuecomment-2081051730
Gotcha
@@ -20,9 +20,19 @@ Turbo.setConfirmMethod((message) => {document.getElementById("turbo-confirm-body").innerHTML = body;I just didn't want to modify the _confirm_modal partial. I'll do that now instead. So we just toggle visibility based on args.
@@ -0,0 +1,7 @@class DeleteUserJob < ApplicationJobGotcha.
@zachgoll , I'll work on the changes and revert.
@@ -9,3 +9,3 @@def createif user = User.authenticate_by(email: params[:email], password: params[:password])if user = User.authenticate_by(email: params[:email], password: params[:password], marked_for_deletion: false)login userprevents users marked_for_deletion from logging in.
@@ -18,2 +18,4 @@enddef destroybeginThis is much simpler I hope.
We just refuse to delete last admin with members - otherwise all goes through
@@ -23,3 +41,4 @@params.require(:user).permit(:first_name, :last_name,family_attributes: [ :name, :id ])endIt read so well. I couldn't help using it. Thanks for the snippet.
@zachgoll , I've made changes based on review. Let me know how the click-testing goes.
@@ -0,0 +4,4 @@def perform(user)user.purge_dataendendThe logic here looks better, but I think we should be handling all of this within
user.rbandfamily.rb. Something along the lines of:@@ -8,0 +14,4 @@enddef can_be_destroyed_by?(user)users.count == 1 && user.admin?This is bringing in context that is not necessary at the family level (
marked_for_deletion).It may be better to instead keep these as basic scopes and let the job logic filter out relevant admins.
Is this ID being used for something? I think we can safely remove it.
It is not being used. Will clean it up.
@@ -8,0 +14,4 @@enddef can_be_destroyed_by?(user)users.count == 1 && user.admin?updated the method to not user the marked_for_deletion query.
Had challenges using scope here though so maintained the method.
@jemiluv8 thanks for the updates here. I think this PR is at a final state now, although in #698 there is a "deactivate" strategy implemented which I think offers a more concise and intuitive way to handle what we had come up here with the
marked_for_deletionflag on theUsermodel.Since there's a lot of good work here that allowed us to see the requirements for this issue more clearly and come to a solid solution, we'll award a split bounty on this.
I'll close this PR out, but there will be a payment awarded after #698 is merged. Thanks for all the work on this!
That's great @zachgoll, I learned a lot and been intrigued by the deactivation strategy.
It looked solid.
@jemiluv8 just an FYI, the bounty split didn't work properly when the PR was merged, but we're working to get that resolved right now so you get the appropriate payout.
Gotcha @zachgoll. Thanks.
Pull request closed