Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I tell when an ActiveRecord::Base.transaction block commits?

A common problem with background jobs and ActiveRecord is when jobs get enqueued and executed before a needed model is committed to the database.

ActiveRecord models have a nice after_commit callback that can be used for a particular model.

But let's say you've got some business logic that touches a few different models, and it's not really appropriate to cram that logic inside a single model. So, you write some sort of service/command object that performs the logic inside a transaction block:

For example, something along the lines of:

class SomeServiceObject

  def execute
    thing = create_thing_in_a_tx

    # this notification often fires before the above transaction commits.
    notify_user(thing)
  end

  private

  def create_thing_in_a_tx
    ActiveRecord::Base.transaction do
      a = ModelA.new(foo: 'bar')
      b = ModelB.new(a_record: a, biz: 'baz')
      #... various other logic that doesn't really belong in a model ...
      ThingModel.create!(b_record: b)
    end
  end

  def notify_user(thing)
    EnqueueJob.process_asyc(thing.id)
  end
end

In this case, as far as I can tell, you don't really have access to the handy after_commit callback.

I suppose in the above example, you could have ThingModel enqueue the job inside of its after_commit callback, but then you're spreading what should be the responsibilities of SomeServiceObject across different classes, and that feels wrong.

Given all of the above, is there any reasonable way to know when a ActiveRecord::Base.transaction block commits, without resorting to any particular model's after_commit callback?

Thank you! :-D

(See also: How to force Rails ActiveRecord to commit a transaction flush)

like image 924
John Reilly Avatar asked Aug 18 '16 16:08

John Reilly


People also ask

What is ActiveRecord base transaction?

Transactions in ActiveRecord We're calling the transaction method on the ActiveRecord::Base class and passing it a block. Every database operation that happens inside that block will be sent to the database as a transaction.

How do rails transactions work?

Rails transactions are tied to one database connectionAnd as long as the transaction block is running this one database connection is open. So try to do as little as needed inside the transaction block, otherwise you will be blocking a database connection for more time than you should.

How do I rollback transactions in rails?

You roll back the transaction by raising the ActiveRecord::Rollback error, but the error isn't raised outside, as happens with other errors. Keep this behavior in mind and use it wisely.

What does pluck do in Rails?

Rails has a great, expressive term called pluck that allows you to grab a subset of data from a record. You can use this on ActiveRecord models to return one (or a few) columns. But you can also use the same method on regular old Enumerables to pull out all values that respond to a given key.


1 Answers

It's simpler than you might think. After the ActiveRecord::Base.transaction block completes, the transaction has been committed.

def create_thing_in_a_tx
  begin
    ActiveRecord::Base.transaction do
      a = ModelA.new(foo: 'bar')
      b = ModelB.new(a_record: a, biz: 'baz')
      #... various other logic that doesn't really belong in a model ...
      ThingModel.create!(b_record: b)
    end
    # The transaction COMMIT has happened.  Do your after commit logic here.
  rescue # any exception
    # The transaction was aborted with a ROLLBACK.  
    # Your after commit logic above won't be executed.
  end
end
like image 200
Bill Doughty Avatar answered Oct 29 '22 05:10

Bill Doughty