I'm curious as to what would require such a construct!
I think you're going to struggle to do it without a bit of hackery as you've described. For example, you could, in mysql, set the table storage type for Inner
to one that doesn't support transactions (MyIsam eg) whilst keeping other classes' tables storage with something that does support transactions (YUK!).
If you can, you'd almost certainly be better off delaying the Inner.create
until after the transaction. You can use begin with ensure to make sure that the create always happens. Something like:
create_inner = false
begin
Outer.transaction.do
...
create_inner = true # instead of Inner.create(:blah)
...
end
ensure
if create_inner
Inner.create(:blah)
end
end
This would become more complicated if the rest of your block depends on the created Inner
instance. You could probably create the instance in the block and set created_inner
to false at the end of the block so that, if the code runs without exception it will have been created in the transaction and you won't create again in the ensure.
If you want to do it in the general case you could define a class method on Inner
to execute a block but always create an Inner
object. You'd need to add an after_create
to Inner
too. You would rely on the Inner.create
call in the block to create it when the transaction succeeds but if it is rolled back then you'd need to create it afterwards. For example:
class Inner < ActiveRecord::Base
def self.ensure_created(&block)
Thread.current[:created_inner] = false
begin
block.call
rescue => e
if Thread.current[:created_inner]
Inner.create(:blah)
end
raise e
end
end
def after_create
# Flag that an instance has been created in this thread so
# that if we rollback out of a transaction we can create again
Thread.current[:created_inner] = true
end
You'd then call it like:
Inner.ensure_created do
Outer.transaction do
...
Inner.create(:blah)
...
end
end
HOWEVER, there's plenty of downsides to this approach and I'm not sure I'd advocate it. It is complicated. It won't work if ActiveRecord::Rollback is raised as that exception won't bubble out of the Outer.transaction
but will cause the Inner
instance not to be created. It won't work properly when two or more calls are nested. And finally I haven't tested it thoroughly - use with caution!