mardi 8 août 2017

ActiveRecord before-transaction callback

Problem: I'm looking for an ActiveRecord (Rails 3.2) callback hook that will execute BEFORE a DB transaction has begun.

If it doesn't exist (and I don't think it does), then I'm looking for any advice in monkey-patching ActiveRecord so that we can implement such a method ourselves.

What I've Tried

Having looked at ActiveRecord docs and playing around with the code, it seems that only 'after_commit' is guaranteed to be executed outside a transaction. Everything else ("before_validation", "before_save", "before_commit") is ran within the transaction. I've tried overriding the 'transaction' method:

class User < ActiveRecord::Base
  included do |base|
    base.instance_eval do
    def before_transaction
      ### DO STUFF
    end

    def transaction(options = {}, &block)
       before_transaction { super(options, &block) }
    end
  end
end

But this is complicated because transaction is a class method and the callback has to be registered per instance of the model. On top of that, it isn't guaranteed to run outside of a DB transaction because it could be nested:

ActiveRecord::Base.transaction do
  ActiveRecord::Base.transaction do
    user.update_attribute(name: "Hodor")
  end
end

Not sure what I want is possible given the way ActiveRecord is implemented, because it may not be possible to know if another transaction has already begun and invoke the callback then. In any case, I thought it's worth asking here.

Background

I'm trying to 'sync' an existing Rails model (let's call it User) to a user service that lives elswhere. The user service should act as the source of truth.

Whenever updates happen to the Rails User model, I would like to validate + commit the change to the user service first, before saving to the DB (which only the Rails project use). The DB for the Rails project then functions as a write-through cache (why a Database for a cache? Legacy code reasons).

If we could call the service BEFORE writing to the DB, we don't have to worry about rolling back the transaction (and dealing with what happens when that rolling back fails!). However, I am hesitant to call the service in the middle of an open DB transaction, which is why I'm looking into whether or not it's possible to build a "before_transaction" callback.

Aucun commentaire:

Enregistrer un commentaire