Docs Menu
Docs Home
/ / /
Mongoid
/

Transactions

On this page

  • Using Transactions
  • Higher Level API
  • Lower Level API

Version 4.0 of the MongoDB server introduces multi-document transactions. (Updates to multiple fields within a single document are atomic in all versions of MongoDB). Transactions require a non-standalone MongoDB topology and Ruby driver version 2.6 or higher. A higher level transaction API requires Mongoid version 9.0 or higher, while a lower level API requires Mongoid version 6.4 or higher.

A transaction can be started by calling the transaction method on an instance of a Mongoid document class, on a Mongoid document class, on or Mongoid module:

Band.transaction do
Band.create(title: 'Led Zeppelin')
end
band = Band.create(title: 'Deep Purple')
band.transaction do
band.active = false
band.save!
end
Mongoid.transaction do
band.destroy
end

When the transaction method is called, Mongoid does the following:

  • creates a session on a client that is used by the receiver of the transaction method call;

  • starts a transaction on the session;

  • executes the given block;

  • commits the transaction if no exception raised in the block;

    • calls after_commit callbacks for all objects modified inside the transaction

  • aborts the transaction if an exception is raised in the block;

    • calls after_rollback callbacks for all objects modified inside the transaction

  • closes the session

Note

Since a transaction is tied to a particular client, _only_ operations on the same client will be in scope of the transaction. Therefore it is recommended that only objects that use the same client are used inside the transaction method block.

class Author
include Mongoid::Document
store_in client: :encrypted_client
end
class User
include Mongoid::Document
store_in client: :encrypted_client
end
class Article
include Mongoid::Document
# This class uses the :default client
end
# Transaction is started on the :encrypted_client
Author.transaction do
# This operation uses the same client, so it is in the transaction
Author.create!
# This operation also uses the same client, so it is in the transaction
User.create!
# This operation uses a different client, so it is NOT in the transaction
Article.create!
end

Note

When transaction method is called on Mongoid module, the transaction is created using the :default client.

Any exception raised inside the transaction method block aborts the transaction. Normally the raised exception passed on, except for the Mongoid::Errors::Rollback. This error should be raised if you want to explicitly abort the transaction without passing on an exception.

Transaction API introduces two new callbacks - after_commit and after_rollback.

after_commit callback is triggered for an object that was created, saved, or destroyed:

  • after transaction is committed if the object was modified inside the transaction;

  • after the object was persisted if the object was modified outside a transaction.

Note

In any case after_commit callback is triggered only after all other callbacks were executed successfully. Therefore, if the object is modified without a transaction, it is possible that the object was persisted, but after_commit callback was not triggered (for example, an exception raised in after_save callback).

after_rollback callback is triggered for an object that was created, saved, or destroyed inside a transaction if the transaction was aborted. after_rollback is never triggered without a transaction.

In order to start a transaction, the application must have a session.

A transaction can be started by calling the start_transaction method on a session, which can be obtained by calling the with_session method on either a model class or instance:

class Person
include Mongoid::Document
end
Person.with_session do |session|
session.start_transaction
end
person = Person.new
person.with_session do |session|
session.start_transaction
end

It is also possible to specify read concern, write concern and read preference when starting a transaction:

Person.with_session do |session|
session.start_transaction(
read_concern: {level: :majority},
write_concern: {w: 3},
read: {mode: :primary})
end

A transaction may be committed or aborted. The corresponding methods to do so are commit_transaction and abort_transaction, again on the session instance:

Person.with_session do |session|
session.commit_transaction
end
Person.with_session do |session|
session.abort_transaction
end

If a session ends with an open transaction, the transaction is aborted.

The transaction commit can be retried if it fails. Here is the Ruby code to do so:

begin
session.commit_transaction
rescue Mongo::Error => e
if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)
retry
else
raise
end
end

Note that in order to perform operations within the transaction, operations must use the same client that the session was initiated on. By default, all operations will be done on the default client:

class Person
include Mongoid::Document
end
class Post
include Mongoid::Document
end
Person.with_session do |s|
s.start_transaction
Person.create!
Person.create!
Post.create!
s.commit_transaction
end

To explicitly use a different client, use the with method:

Post.with(client: :other) do
Person.with(client: :other) do
Person.with_session do |s|
s.start_transaction
Person.create!
Person.create!
Post.create!
s.commit_transaction
end
end
end

Back

Sessions