The problem is that database_cleaner calls ActiveRecord.rollback at the end of a test -- which you also use in your code. InnoDB/MySQL does not support true nested transactions so nested transactions in code are not treated truly as transactions unless they are explicitly called to do so.
Consider this block (from the ActiveRecord docs):
User.transaction do
User.create(username: 'Kotori')
User.transaction do
User.create(username: 'Nemu')
raise ActiveRecord::Rollback
end
end
What do you expect to happen after the Rollback call? You expect it to roll back the Nemu user, and leave you with Kotori, right? Well, what actually happens is that Kotori and Nemu are both created. The Rollback doesn't trigger because you're in a nested transaction (and AR only cares about the parent, currently) and the parent transaction (which has an actual db transaction) does not see the Rollback call -- as it is called in an isolated block. It's weird.
Solution:
Klass.transaction(requires_new: true)
If you set requires_new, ActiveRecord will use or pseudo-use a nested transaction (for Postgres it'll make nested transactions, for MySQL/InnoDB it will make savepoints). When you call a rollback with ActiveRecord, it'll figure out its scope and issue the proper rollback.
The other solution is to use truncation or deletion as a strategy for your tests that involve transactions.