Does anyone have a good reference on details of when AR opens transactions and locks? I know the broad strokes, but had trouble really finding all the spots involved in the codebase and don’t see any decent write-ups online.
When in doubt, use the source.
I highlighted the primary locations. Take save
for example. The core of that method is in the Persistance
module. But other modules “wrap” that via inheritances such as the Transactions
model. You can see the list of modules here although obviously not all of these modules wrap save
. But for the ones that do the later ones wrap the earlier ones. Since Transactions
is one of the last modules it would wrap most of the modules. Take Callbacks
for example. This tells you that a transaction is started and then within that transaction the callbacks execute. You could search for the save
symbol on other modules to see what happens before, within and after the transaction. Then do that for other methods that wrap base functionality with a transaction. Also look at that Transaction
module itself for things such as the after_commit
callback, etc.
I appreciate the pointer within the source.I had looked at that a bit but the main issues were that I am not sure if it is really comprehensive and that there are not any other places where transactions are touched / modified, particularly because nested transactions could exist and cause more complex behavior. Or said differently, in a complex codebase, it is hard to confirm you have found all the code related to a topic while it is easy to find some of the code related to a topic.
Yea, I’m sure that’s not comprehensive but it’s the main chunk when dealing with ActiveRecord.
At a lower-level the connection itself has stuff relating to transactions that is probably worth reading. That has info about nested transactions. The TLDR is nested transactions (savepoints in SQL parlance) are not generally used unless specifically opted in via the requires_new
option. There is a bit other info in there about isolation levels although in all my years of doing Rails I can’t think of a time I’ve used them.
The connection object also has a module mixed in that allows you to query about the transaction state that could be useful but again it’s not something I’ve used despite many years of Rails development.
There is a little section in the migrations documentation regarding disabling DDL transactions for certain migrations being needed sometimes. I have used this at various times such as certain PG statements where the WITH CONCURRENCY
option is used.
Finally you mentioned locks in your original post but I missed that the first time. Rails has two forms of locking.
-
Optimistic locking - This is the greatly preferred method if it works for your use case. The documentation doesn’t make it very clear but putting the
lock_version
as a hidden field in your HTML form allows you to ensure that two users editing the same record don’t overwrite each other. The second person will get an error forcing them to go back and get fresh data that incorporates the change of the first person. - Pessimistic locking - I’ve rarely used this but there have been a few edge cases where it has been handy. Usually some sort of background processing.