Locked record after user abandonment - ruby-on-rails

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?

Related

Adobe Analytics - PurchaseID set with Timestamp Issue

We are having a lot of transactions on the site, so for this reason we are re-cycling our booking confirmation numbers/ order id numbers on the confirmation screen which is set into our purchaseID . Since we are re-using our booking confirmation number, in order to make our purchaseID unique we are adding timestamp to our purchaseID variable using pipe delimeter. So formula looks like:
purchaseID = order_id + '|' + timestamp (current date).
My concern here is, let's say I make a booking today and my purchase id looks like -
purchaseID = 5747118 | 6-7-2019
Now I access my confirmation screen again tomorrow and after 2 days, 3 days and so on and I see adobe calls firing. Because I accessed my confirmation page on different dates my timestamp changed and thus my purchaseID is not unique anymore. Even though I am seeing my same booking confirmation page my purchaseID is not unique now. Does this mean, every time i view my confirmation screen on a different day my booking/revenue would be counted multiple times ? If yes, what's the best way to tackle this issue ?
So it sounds like someone can go to your site, make a purchase and see the confirmation page, and then later on, go back to the same confirmation page without actually making another purchase. Maybe they bookmarked the page and come back to it later for reference. Or maybe they refreshed the page, because reasons.
Does your site charge their credit card for accessing the page again? I sure hope not. Your site/coding should be structured in a way that does not keep charging the customer more money every time they view the page again.
And your code logic for outputting Adobe Analytics should be structured in the same way: your coding logic should be that you only output purchase event and variables (e.g. purchaseID) when a purchase actually occurs.
In practice, this sometimes isn't easy to do because of how the site is structured. So part of why purchaseIDexists is to de-duplicate purchases, so that if purchase event and data is re-popped, it will be de-duped. But it only works if you output the same purchaseID when the visitor refreshes the page or otherwise comes back to it later on (where they aren't actually making another purchase).
Which it sounds like you were doing with the original booking confirmation number you pushed to purchaseID. But things went south when you decided to throw a current datestamp into the mix because you started recycling booking confirmation numbers. Well you can't do that. You can use a dynamic value such as the current date/timestamp as part of the value, but you must remember it, and output it in the future.
Maybe this involves adding an extra column to your database with the date/timestamp of purchase (which I have to assume you surely already have), and then pull that value when you pull the booking confirmation number.
Or maybe the solution involves stepping back and rethinking the fact you are recycling booking confirmation numbers. This seems like a bad idea to me. It's definitely a bad idea for your Adobe Analytics implementation, as you have seen for yourself. But is this not a bad idea in general? What happens if a customer buys something today and has # 12345 as proof of transaction to reference, and then tomorrow, a week, a year or whatever from now, some other customer gets the same number?
It stands to reason that you will end up with a mess on your hands, trying to sort out which customer bought what. Transaction ids by their nature are supposed to be forever unique to the transaction. So my very first recommended solution to you would be to stop recycling your booking confirmation numbers. Move to a different format if you need to (e.g. UUID).
Failing that, my next recommendation would be what I said a couple paragraphs up, about storing the date/timestamp in a column at the actual time of purchase (which surely you already have), and then grab and use that value along with the booking confirmation # to use as delimited value, instead of generating the current date on the fly (which absolutely does not work).

Soft deleting huge user accounts with a background task

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.

Rails - Create separate table or bypass all model validation?

I am currently using wepay with rails. Don't worry this post is nothing about wepay.
So when a customer wants to buy something from my site, he/she will be redirected to wepay.
Then after paying on wepay, wepay will redirect the user to /purchases/received
After X amount of time, Wepay will also do a post call to /purchases/callback to tell me that the payment has been captured (credit card processing is slow)
So my original plan is as follows:
For the Purchase model, have a field, wepay_id and wepay_confirmed.
When the user place an order on wepay, the redirection to /puchases/received will create a purchase instance and save in my db
When the callback is called look up by wepay_id and then set wepay_confirmed to true.
However, as I discovered that the X amount of time could be so fast that /purchases/callback is called before /purchases/received could create the object.
So now I have two options:
Allow /purchases/callback to create an empty Purchase instance with just the id and confirmed = true. As I was doing this, I realized that I no longer can validate my model in the traditional manner. This really bugs me.
Create a separate table called Wepay_Confirmed. Whenever callback is called, create an entry in wepay_confirmed. Map the presence of an (checkout_id) in this table to Purchase.confirmed attribute.
I am thinking of doing 2. How can I do this? Do I have to generate a scaffold for a specific model to map to Wepay_Confirmed?
If you have any other suggestions, please reply
I would try to keep your application the way it is because it does make sense however you should look into returning an error code to wepay and have them submit the request later after the record is created.
Just emailed the developers over at WePay and got this response:
Hi Devin,
We do have automatic IPN retries. Retries happen 5 minutes after the
initial try, if the retry doesn't work, we try 15 minutes later, and
then an hour later. However, right now they are only on empty 404
responses.
The best solution is to actually just ignore the IPN if he does not
have the record in his database. Our IPNs only tell an application to
look up the checkout details with the /checkout call. They do not have
any details of the checkout. Since he should be looking up the
/checkout status anyway when he creates the checkout object on his
end, he doesn't need the IPN to tell him to look up the status in this
case.
If that doesn't work for him he can also email me at api#wepay.com and
we may be able to work out a solution.
Andrew
So it looks like you can modify the flow of you application to ignore the IPN's without a record and check manually or you can respond with a 404 and they will retry at the above intervals.
As I mentioned in my comment, I would personally prefer to create the purchase record upon purchase, then send the user to the WePay site, then handle the return trip and callback as actions to be completed against that original purchase site.
For one, that matches the reality of the transaction more accurately. When a user makes a purchase from your site, it makes sense to me that it's something you should persist at that point.
The two elements of the WePay transaction (the return trip to your site and the charge confirmation callback) would all act on that original purchase record. This will also allow you to see how many people abandon the purchase process when they hit WePay, which could reveal issues in your user experience that might help to maximize conversions.
I created a gem called wepay-rails which handles all of this for you. Under the hood it creates the entry (WepayCheckoutRecord) before sending the payer off to wepay. It has an IPN listener built in that handles the updating of that record. In my personal rails app, I am using state machine on the WepayCheckoutRecord model to track the changes to the state and doing 'things' as the state changes on that record.
I hope that helps.
Adam -
If you take the 2nd approach, you dont need to scaffold it. You can just create a migration and use it inside one of your other 'scaffolds'. Scaffolds are really just a way to get started with a resource. I dont think your intent here is to have a fully-fledged resource. Unless it is then you can use it as a scaffold.

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.

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