Soft deleting huge user accounts with a background task - ruby-on-rails

I'm using Rails and devise for an app that stores a lot of data on users. I want to make it possible for a user to deactivate his account similar to the way Facebook does it so that if you log back in, your account gets reactivated. So far I've tackled this using a soft delete. Problem is, when people delete their accounts, there's so much data that needs to be soft deleted that it takes a while to run. Naturally, my instinct was to use delayed_job for this. But the problem is, this only works on account deletion, not on reactivation. I don't want my users to have to sit around for 10 second while all their data is restored, but I also can't do it in the background, because then they'll be logged back in before any of their data has been restored.
Any ideas as to how to go about solving this problem?
Thanks in advance!

You'll want to set your soft_delete to be tracked as a boolean flag on all the relevant records. Set your default scopes to return only records that don't have the flag set. When it comes time to activate or deactivate, gather all your relevant records and hit them with update_all. Here's an example run against 13,000 user records to give you a sense of time & performance:
1.9.2p320 :001 > User.update_all(soft_deleted: false)
SQL (1016.3ms) UPDATE "users" SET "soft_deleted" = 'f'
=> 13350
As you see, it hit all 13,000 records with that flag toggle in about one second. So, if you wanted to hit a User, all of a user's Posts, and all of a users PrivateMessages,
User.update_attributes(soft_deleted: true)
User.posts.update_all(soft_deleted: true)
User.private_messages.update_all(soft_deleted: true)
And you should be good to go. If you're dealing with so many records that even this technique doesn't perform well, I don't think you're going to have much choice except to tell the user it may be a few moments before all their data is available on reactivation and throw the whole process into a background job, as you originally planned.

Related

Admin user approving SQL changes from a user

I've read into Cancan and Pundit (also Devise) for managing users in a Rails App. But I wanted to know if something was possible.
Basically, I want to have users change/add lines in a table (using SQLite at the moment, but will be moving to SQL in the future - call them entries). But before it gets added to the actual table, it gets sent to the admin for approval. Then the admin can just hit 'approve' and the statement gets run.
I'm just confused about how to hold the statement and then when approved, the statement runs. Any information would be appreciated.
By statement, do you mean that your users are actually writing the SQL themselves?
If not, I'd setup a second model identical to your first that acts like a queue of some sort that holds the proposed changes/additions. This way, you'll be able to compare the old and new statements if necessary, and, when approved, you'll be able to perform the create/update magic on your original model.
Hope this gives you some ideas!

Locked record after user abandonment

I have a locking challenge in my application. There is an invoice which gets created automatically when a timesheet is approved. The invoice needs to be adapted manually by an employee (adding VAT code, contact data, etc), so he clicks the adapt button. At that moment the timesheet gets the status locked so no changes can be made anymore to the timesheet.
This all works fine, except for one edge case: the employee clicks the adapt button but then doesn't do anything to the invoice. E.g. it goes to another screen, forgets about the invoice, etc. In that case the timesheet stays in locked state forever, while it should be unlocked.
What can I do to solve this? My current idea is to create a rake task which checks timesheets in locked state at a certain interval and removes the locked state when there is more than an hour gone between the current time and the timesheet updated_at time.
But maybe there is another strategy that can handle this case much better?
You can use the approach of gmail eg(when you leave the page before finishing your email he asks if you are really sure you want to leave this page if yes he discards the message)
in your case you should do the same and send an ajax request to unlock the record
In all cases you will need to do the rake task just to handle cases shut as power switch off or browser crashing. but it would give better convenience to avoid locking a record for a whole hour except in very special cases such these
The current strategy you're using is a pessimistic one, I'd recommend using an optimistic strategy:
http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
The result is if someone opens the page, goes to lunch and in the meantime another user updates the record, then when the user gets back from lunch and submits the form an exception will occur.
The next question will be how do you handle the exception? Tell the user this has already been dealt with? Show the user the conflicts so they can resolve them?

Rails 'Preview State' Using Cookies/Session

This seems to be a common question, but I've yet to find a very 'detailed' answer - I'm looking for the best way to allow users to preview the form they are going to submit, before submitting it.
Obviously, storing this in the db isnt the best option (which is what I'm doing right now) because there has to be a process to remove it, and that has a lot of scenarios.
Details
I don't want to show the user the edit action, I want to show the user the show action, and at the top and bottom of the screen have a Publish button.
My questions:
How would you go about storing the information from the form into a session (looking for some controller code)
How would you know in the show action that you are displaying a preview, rather than a 'real' object. (or would you have a separate action for preview?) (looking for some controller/view code here)
How would you remove the session data once the user is 'done' with it?
I think that storing in the database has the same issue with storing it in the session, the cleanup, am I wrong? What is the best way to do this?
You can consider unpublished records created more than few days ago as obsolete (user must know about that policy). Rake task may be executed via cron once a day or more frequently. Also, you can implement some separate rake task to notify users about pending unpublished records, which will be removed soon. Drafts is a great idea and may be untouched if you like at all or for a longer time.

How do I update only the properties of my models that have changed in MVC?

I'm developing a webapp that allows the editing of records. There is a possibility that two users could be working on the same screen at a time and I want to minimise the damage done, if they both click save.
If User1 requests the page and then makes changes to the Address, Telephone and Contact Details, but before he clicks Save, User2 requests the same page.
User1 then clicks save and the whole model is updated using TryUpdateModel(), if User2 simply appends some detail to the Notes field, when he saves, the TryUpdateModel() method will overwrite the new details User1 saved, with the old details.
I've considered storing the original values for all the model's properties in a hidden form field, and then writing a custom TryUpdateModel to only update the properties that have changed, but this feels a little too like the Viewstate we've all been more than happy to leave behind by moving to MVC.
Is there a pattern for dealing with this problem that I'm not aware of?
How would you handle it?
Update: In answer to the comments below, I'm using Entity Framework.
Anthony
Unless you have any particular requirements for what happens in this case (e.g. lock the record, which of course requires some functionality to undo the lock in the event that the user decides not to make a change) I'd suggest the normal approach is an optimistic lock:
Each update you perform should check that the record hasn't changed in the meantime.
So:
Put an integer "version" property or a guid / rowversion on the record.
Ensure this is contained in a hidden field in the html and is therefore returned with any submit;
When you perform the update, ensure that the (database) record's version/guid/rowversion still matches the value that was in the hidden field [and add 1 to the "version" integer when you do the update if you've decided to go with that manual approach.]
A similar approach is obviously to use a date/time stamp on the record, but don't do that because, to within the accuracy of your system clock, it's flawed.
[I suggest you'll find fuller explanations of the whole approach elsewhere. Certainly if you were to google for information on NHibernate's Version functionality...]
Locking modification of a page while one user is working on it is an option. This is done in some wiki software like dokuwiki. In that case it will usually use some javascript to free the lock after 5-10 minutes of inactivity so others can update it.
Another option might be storing all revisions in a database so when two users submit, both copies are saved and still exist. From there on, all you'd need to do is merge the two.
You usually don't handle this. If two users happen to edit a document at the same time and commit their updates, one of them wins and the other looses.
Resources lockout can be done with stateful desktop applications, but with web applications any lockout scheme you try to implement may only minimize the damage but not prevent it.
Don't try to write an absolutely perfect and secure application. It's already good as it is. Just use it, probably the situation won't come up at all.
If you use LINQ to SQL as your ORM it can handle the issues around changed values using the conflicts collection. However, essentially I'd agree with Mastermind's comment.

Allow users to remove their account

I am developing a gallery which allows users to post photos, comments, vote and do many other tasks.
Now I think that it is correct to allow users to unsubscribe and remove all their data if they want to. However it is difficult to allow such a thing because you run the risk to break your application (e.g. what should I do when a comment has many replies? what should I do with pages that have many revisions by different users?).
Photos can be easily removed, but for other data (i.e. comments, revisions...) I thought that there are three possibilities:
assign it to the admin
assign it to a user called "removed-user"
mantain the current associations (i.e. the user ID) and only rename user's data (e.g. assign a new username such as "removed-user-24" and a non-existent e-mail such as "noreply-removed-user-24#mysite.com"
What are the best practices to follow when we allow users to remove their accounts? How do you implement them (particularly in Rails)?
I've typically solved this type of problem by having an active flag on user, and simply setting active to false when the user is deleted. That way I maintain referential integrity throughout the system even if a user is "deleted". In the business layer I always validate a user is active before allowing them to perform operations. I also filter inactive users when retrieving data.
The usual thing to do is instead of deleting them from a database, add a boolean flag field and have it be true for valid users and false for invalid users. You will have to add code to filter on the flag. You should also remove all relevant data from the user that you can. The primary purpose of this flag is to keep the links intact. It is a variant of the renaming the user's data, but the flag will be easier to check.
Ideally in a system you would not want to "hard delete" data. The best way I know of and that we have implemented in past is "soft delete". Maintain a status column in all your data tables which ideally refers to the fact whether the row is active or not. Any row when created is "Active" by default; however as entries are deleted; they are made inactive.
All select queries which display data on screen filter results for only "active records". This way you get following advantages:
1. Data Recovery is possible.
2. You can have a scheduled task on database level, which can take care of hard deletes of once in a way; if really needed. (Like a SQL procedure or something)
3. You can have an admin screen to be able to decide which accounts, entries etc you'd really want to mark for deletion
4. A temperory disabling of account can also be implemented with same solution.
In prod environments where I have worked on, a hard delete is a strict No-No. Infact audits are maintained for deletes also. But if application is really small; it'd be upto user.
I would still suggest a "virtual delete" or a "soft delete" with periodic cleanup on db level; which will be faster efficient and optimized way of cleaning up.
I generally don't like to delete anything and instead opt to mark records as deleted/unpublished using states (with AASM i.e. acts as state machine).
I prefer states and events to just using flags as you can use events to update attributes and send emails etc. in one foul swoop. Then check states to decide what to do later on.
HTH.
I would recommend putting in a delete date field that contains the date/time the user unsubscribed - not only to the user record, but to all information related to that user. The app should check the field prior to displaying anything. You can then run a hard delete for all records 30 days (your choice of time) after the delete date. This will allow the information not to be shown (you will probably need to update the app in a few places), time to allow the user to re-subscribe (accidental or rethinking) and a scheduled process to delete old data. I would remove ALL information about the member and any related comments about the member or their prior published data (photos, etc.)
I am sure it changing lot since update with Data Protection and GDPR, etc.
the reason I found this page as I was looking for advice because of new Apply policy on account deletion requirements extended https://developer.apple.com/news/?id=i71db0mv
We are using Ruby on Rails right now. Your answers seem a little outdated? or not or still useful right now
I was thinking something like that
create a new table “old_user_table” with old user_id , First name, Second name, email, and booking slug.
It will allow keep all users who did previous booking. And deleted their user ID in the app. We need to keep all records for booking for audit purpose in the last 5 years in the app.
the user setup with this app, the user but never booking, then the user will not transfer to “old_user_table” cos the user never booking.
Does it make sense? something like that?

Resources