Interesting, I went to google, and the link given for Choose a single layer of cleverness is currently dead.
Where I work we have 40 or so developers, each with their own idea of how to model RI and such. We have two devdb folk, and I'm one of them. We're the gate keepers of the RI model, and we make sure that our airline reservation system doesn't just suddenly forget something by use of RI. No matter how well thought out the framework, or how noble the meanings of the developers, they make some really stupid decisions, day in and day out, that we have to rework into a usable data model that is both fast and efficient, AND robust. If we left it up to the developers, we'd have a pile of crap no one could keep running.
Doing RI in the app is inefficient. Maybe you don't work on a system that's designed to handle 100,000 reservations a day. I do. And if we did RI in the app we'd be farked. How can I have 12 app servers handle RI independently? Answer? I can't. So, they'd all have to talk to each other every time they tried to commit. The amount of interaction between them would increase at a severely non-linear rate as the load increased, and the whole thing would collapse from either exhausting the network or the app server CPUs. meanwhile, oracle or postgresql can handle the RI in their sleep.
RI in the app is one of those "seemed like a good idea at the time" things that bites you in the ass with non-scalability at a later date.
An example of a scalability issue and how we solved it. We had a table with locatorcodes, which are 6 character alphanms with A-Z0-9 characters. In the airline industry, the locatorcode is the key to the record. You have to check one out of a table with millions available in it everytime you create a reservation.
Our old way did it app centric. Ask the database for one with the used field set to 0, set the used field to 1, and go on. This took about 0.2 to 0.5 seconds on a loaded system. that may not seem much, but it was a large part of our load. And the time was going up exponentially as more processes were checking them out due to locking issues.
We changed it. We now have a sequence (something real databases like oracl e and postgresql have) and we have an id for each row. You get the next sequence, grab the corresponding row, knowing a sequence never repeats, and mark the row used, which is really kind of a formality we may even drop at some point.
Time to run that stored proc in oracle on a heavily loaded db? 0.0001 seconds.
Those kinds of things are what you have to do in the real world to get the performance needed to run a large transactional system. when I see someone building a large test system with ROR that can handle load even 1/10th that of one relying on the database with stored procs, I will buy the chief architect a beer, in person, along with a pizza.
It's not going to happen with RI in the app. period.