68

I currently use Devise for user registration/authentication in a Rails project. When a user wants to cancel their account, the user object is destroyed, which leaves my application in an undesired state.

What is the easiest way to implement a "soft delete", i.e. only removing personal data and marking the user as deleted? I still want to keep all record associations.

I assume I will have to first introduce a new "deleted" column for users. But then I am stuck with this default code in the user's profile view:

<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>

Where can I find the :delete method? How should I overwrite the default Devise methods?

slhck
  • 36,575
  • 28
  • 148
  • 201
  • What should happen when the user is deleted? should all his posts/activity be deleted or what? – khelll Feb 28 '11 at 10:25
  • I would like to retain all associations with other objects but just remove personal data (name/e-mail address) and disable login. – slhck Feb 28 '11 at 10:34

5 Answers5

104

I could advise overriding destroy method on your User model to simply do update_attribute(:deleted_at, Time.current) (instead of actually destroying), but this deviation from standard API could become burdensome in the future, so here's how to modify the controller.

Devise has a bunch of default controllers out of the box. The best way to customize them is to create your own controller inheriting the corresponding devise controller. In this case we are talking about Devise::RegistrationsController — which is easily recognized by looking at source. So create a new controller.

class RegistrationsController < Devise::RegistrationsController
end

Now we have our own controller fully inheriting all the devise-provided logic. Next step is to tell devise to use it instead of the default one. In your routes you have devise_for line. It should be changed to include registrations controller.

devise_for :users, :controllers => { :registrations => 'registrations' } 

This seems strange, but it makes sense because by default it's 'devise/registrations', not simply 'registrations'.

Next step is to override the destroy action in registrations controller. When you use registration_path(:user), :method => :delete — that's where it links. To destroy action of registrations controller.

Currently devise does the following.

def destroy
  resource.destroy
  set_flash_message :notice, :destroyed
  sign_out_and_redirect(self.resource)
end

We can instead use this code. First let's add new method to User model.

class User < ActiveRecord::Base
  def soft_delete
    # assuming you have deleted_at column added already
    update_attribute(:deleted_at, Time.current)
  end
end

# Use this for Devise 2.1.0 and newer versions
class RegistrationsController < Devise::RegistrationsController

  def destroy
    resource.soft_delete
    Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
    set_flash_message :notice, :destroyed if is_navigational_format?
    respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
  end
end

# Use this for older Devise versions
class RegistrationsController < Devise::RegistrationsController
  def destroy
    resource.soft_delete
    set_flash_message :notice, :destroyed
    sign_out_and_redirect(resource)
  end
end

Now you should be all set. Use scopes to filter out deleted users.

reves
  • 125
  • 1
  • 9
Max Chernyak
  • 37,015
  • 6
  • 38
  • 43
  • Thank you for the answer, I've been able to cook my own solution with your input! – slhck Feb 28 '11 at 12:11
  • 1
    Great thanks for the answer! Please fix the typo: **update_attribtue** – kravc Feb 22 '12 at 13:28
  • 2
    How can I let the user re-signup at a later time with the same email? – Jonathan Roy May 04 '13 at 19:09
  • @elsurudo, I found this to do it: http://stackoverflow.com/questions/8886317/undelete-acts-as-paranoid-deleted-user-on-devise-sign-in – Jonathan Roy May 08 '13 at 21:16
  • 2
    Make sure to also override the new sessions controller to not allow people to sign in if they've deleted their account. – corbin Sep 12 '13 at 21:10
  • A similar example is also on the devise wiki: https://github.com/plataformatec/devise/wiki/How-to:-Soft-delete-a-user-when-user-deletes-account – Stratus3D Jan 19 '16 at 01:31
95

Adding onto hakunin's answer:

To prevent "soft deleted" users from signing in, override active_for_authentication? on your User model:

def active_for_authentication?
  super && !deleted_at
end
Community
  • 1
  • 1
Peyman
  • 1,228
  • 10
  • 13
  • 3
    +1 for pointing out the correct hook that also kicks out newly deleted users from their current session. – Martin T. Nov 23 '11 at 17:45
  • 2
    This is really the best answer. [Here are the docs](http://rubydoc.info/github/plataformatec/devise/master/Devise/Models/Authenticatable). – d_ethier Dec 10 '12 at 19:29
  • 6
    Thanks for this, the only part I didn't like was the message being shared with the account not confirmed. See my answer on how to change that message: http://stackoverflow.com/a/14966003/637094 – Leonel Galán Feb 19 '13 at 19:47
  • To add to d_either's reference, it seems like you need to also define inactive_message in addition to active_for_authentication? on your User model. This is to provide a message for why the active_for_authentication? would return false. – Joey May 05 '14 at 15:05
  • 1
    If you want the same validation message as if the user record was actually not present, you might want to define inactive_message as: `def inactive_message return self.active ? super : I18n.t('devise.failure.not_found_in_database') end` – Joey May 05 '14 at 15:14
10

You could use acts_as_paranoid for your User model, which sets a deleted_at instead of deleting the object.

rausch
  • 3,148
  • 2
  • 18
  • 27
  • I've heard from that one before. How would it interact with Devise? Where would I need to change anything apart from installing `acts_as_paranoid`? – slhck Feb 28 '11 at 10:35
  • @shick ...you should add `acts_as_paranoid` line to your User model(or any other model you need soft delete). – rubyprince Feb 28 '11 at 11:05
  • 1
    This gem has a lot of forks/versions and I could not find a working one for Rails 3.2.x – Ivailo Bardarov Dec 10 '12 at 14:38
  • 3
    @IvailoBardarov There's a new gem called paranoia for Rails 3: https://github.com/radar/paranoia – graywh Mar 12 '13 at 15:58
7

A complete tutorial can be found at Soft Delete a Devise User Account on the Devise wiki page.

Summary:
1. Add a "deleted_at" DATETIME column
2. Override users/registrations#destroy in your routes
3. Override users/registrations#destroy in the registrations controller
4. Update user model with a soft_delete & check if user is active on authentication
5. Add a custom delete message

csi
  • 9,018
  • 8
  • 61
  • 81
0
  def devise_current_user
   @current_user ||= warden.authenticate(scope: :user)
  end

  def current_user
    if params[:user_id].blank?
      devise_current_user
    else
      User.find(params[:user_id])
    end
  end
sanyam
  • 1
  • 1
  • 3
    Do not post an answer with merely codes. While your solution can be useful, you should also explain why the code will fix the problem that was described in the question. – 4b0 Dec 21 '22 at 13:30