ruby-on-railsrubystaledataexception

Is there a built-in approach in Ruby on Rails to avoid writing data in stale forms to the database?


In my app, multiple users can edit the same object in the database. If user1 loads data into a form, yet user2 writes data to the database AFTER user1 loads the form but before user1 submits the data, I would like to prevent the save of user1's data.

Is there anything built-in to Rails, or available in the Ruby/Rails community libraries that aims to solve this problem?

I could attempt it myself, by having a virtual attribute on all models read_at that all UI elements send back. But I know problems can be more complicated than they seem, and if there's a built-in solution, I'd like to know.

Barring a pre-built solution, are there Rails-specific characteristics I might consider using? I know there is a form token? I wonder if I could get time info from that?


Solution

  • The link to ActiveRecord::Locking::Optimistic in the answer by Andreas Gebhard technically says all you need to know. However, it might be more clear if I spelled it out for you.

    If you add a field named lock_version in your table, by default ActiveRecord will enable optimistic locking. However you are not done yet.

    The problem you are having is called "The Lost Update Problem" where a second person's update clobbers an earlier person's update. If all you do is enable optimistic locking in the database layer you will still have a lost update problem in your web application if you program it normally and do not take the necessary steps.

    Normally your update action does a find of a record, then replaces all the fields with values sent from the form. As far as the database and ActiveRecord know, you started with a fresh recent record and updated it. Thus optimistic locking will be satisfied with allowing the update but you still have a lost update problem.

    What you need to do is include the lock_version field as a hidden input in your form. Then when the second person doing an update tries to update, setting the lock_version to the old value before the prior update was done will cause optimistic locking to fail.

    In pessimistic locking, the key to avoiding the lost update problem is to get the edit form you must first get the lock. If someone else has it locked, you cannot edit. A very important key to solving the lost update problem is the human in the loop by presenting the values in the database before their edit so only the human makes a decision to change them. In the lost update someone did not know about someone else's update and so saved the previous value unaware that it had been updated.

    In reality there is no solution to the problem. Someone will always lose. In the lost update problem the first person to save loses. In pessimistic (aka exclusive locking) the second person to try to get the lock loses. In optimistic locking the second person to save loses.

    The downside with pessimistic/exclusive locking is someone trying to get the edit form is denied because someone else has the lock. Also locks might not get released when they should and you can have deadlocks.

    The downside with optimistic locking is someone can do all the work on their edit and be denied when they try to save.

    In any case it is a surprise to someone just trying to edit. The downside of the lost update problem which makes it the worst is the surprise happens quietly without anyone noticing. At least with exclusive or optimistic locking the user gets informed. With optimistic locking they have to "merge" possibly having to redo the work of their edit starting from fresh data. With exclusive locking they never have that problem but they may have to wait and might not understand why. The general preference for optimistic locking is that most of the time there is no contention for the update and it just works whereas for exclusive locking there is always the step of going into edit mode and refreshing the data on the edit form.