Transactional callbacks in Ruby on Rails

There are two additional callbacks that are fired when a database transaction completes: after_commit and after_rollback. These callbacks are very similar to the after_save callback, except that they are not fired until the database changes have been committed or reversed. They are most useful when your Active Record models need to interact with external systems that are not part of the database transaction.

Consider, for example, the previous example where the PictureFile needs to delete the file after the record is destroyed. If anything throws an exception after the after_destroy callback has been called and the transaction is rolled back, the file will be deleted and the model will be left in an inconsistent state. For example, suppose picture_file_2 in the following code is not valid and the save! will cause an error.

PictureFile.transaction do

  picture_file_1.destroy

  picture_file_2.save!

end

Using the after_commit callback, this case can be taken into account.

class PictureFile < ApplicationRecord

  after_commit :delete_picture_file_from_disk, on: :destroy

  def delete_picture_file_from_disk

    if File.exist?(filepath)

      File.delete(filepath)

    end

  end

end

NOTE: The :on option determines when the callback will run. If you don’t provide the :on option, the callback will be fired for each action.

Since it is customary to use the after_commit callback only when creating, updating, or deleting, there are aliases for these operations:

after_create_commit

after_update_commit

after_destroy_commit

class PictureFile < ApplicationRecord

  after_destroy_commit :delete_picture_file_from_disk

  def delete_picture_file_from_disk

    if File.exist?(filepath)

      File.delete(filepath)

    end

  end

end

WARNING: The after_commit and after_rollback callbacks are called on all created, updated, or deleted models within a transaction block. However, if any exception is thrown in one of these callbacks, that exception will be bubbled up and any remaining after_commit or after_rollback methods will not be executed. Basically, if your callback code could throw an exception, you should call rescue on it, and handle it in the callback to allow other callbacks to run.

WARNING. When using after_create_commit and after_update_commit at the same time on the same model, only the last defined callback will fire, overriding all others.

class User<ApplicationRecord

  after_create_commit :log_user_saved_to_db

  after_update_commit :log_user_saved_to_db

  private

  def log_user_saved_to_db

    puts ‘User was saved to database’

  end

end

# output nothing

>> @user = User.create

# update @user

>> @user.save

=> User was saved to database

To register callbacks for both create and update actions, use after_commit.

class User<ApplicationRecord

  after_commit :log_user_saved_to_db, on: [:create, :update]

end

Active Record Links

This guide covers the specifics of Active Record links.

After reading it, you will know:

How to declare relationships between Active Record models.

How to understand the different types of Active Record relationships.

How to use methods added to your models when creating relationships.

Why are connections needed?

In Rails, a relationship is a connection between two Active Record models. Why do we need links between models? Because they allow you to make the code for common operations simpler and easier. For example, consider a simple Rails application that includes an author model and a book model. Each author can have many books. Without relationships, the model declaration would look like this:

classAuthor<ApplicationRecord

end

classBook < ApplicationRecord

end

Now let’s say we want to add a new book for an existing author. We need to do this:

@book = Book.create(published_at: Time.now, author_id: @author.id)

Or, let’s say we delete the author and make sure that all his books will also be deleted:

@books = Book.where(author_id: @author.id)

@books.each do |book|

  book.destroy

end

@author.destroy

With Active Record relationships, you can simplify these and other operations by declaratively telling Rails that there is a connection between two models. Here is the revised code for creating authors and books:

classAuthor<ApplicationRecord

  has_many :books, dependent: :destroy

end

classBook < ApplicationRecord

  belongs_to :author

end

With these changes, creating a new book for a specific author is easier:

@book = @author.books.create(published_at: Time.now)

Removing an author and all his books is much easier:

@author.destroy

To learn more about the different types of links, read the next section of the guide. This is followed by some helpful hints for working with relationships, followed by a full description of the methods and options for relationships in Rails.

Relationship types

Rails supports six types of links:

belongs_to

has_one

has_many

has_many :through

has_one :through

has_and_belongs_to_many

Relationships are implemented using macro-style calls, so you can add features to your models declaratively. For example, by declaring that one model belongs (belongs_to) another, you tell Rails to keep the primary-foreign key information between instances of the two models, and you get a few useful methods added to the model.

After reading this entire tutorial, you will learn how to declare and use various forms of relationships. But first, you should quickly familiarize yourself with the situations when each type of communication is applicable.

belongs_to relationship

A belongs_to relationship establishes a one-to-one connection with another model when one instance of the declaring model “belongs” to one instance of the other model. For example, if your application has authors and books, and one book can only be associated with one author, you would declare the book model as follows:

classBook < ApplicationRecord

  belongs_to :author

end

Diagram for belongs_to relationship

NOTE: belongs_to relationships must be singular. If you use the plural in the example above for the author relationship in the Book model, you will be told “uninitialized constant Book::Authors”. This is because Rails automatically derives the class name from the association name. If a number is incorrectly used in the association name, the resulting class will also be the wrong number.

The corresponding migration might look like this:

class CreateOrders < ActiveRecord::Migration[5.0]

  def change

    create_table :authors do |t|

      t.string :name

      t.timestamps

    end

    create_table :books do |t|

      t.belongs_to :author, index: true

      t.datetime :published_at

      t.timestamps

    end

  end

end

has_one connection

The has_one relationship also establishes a one-to-one connection with another model, but in a slightly different sense (and with different implications). This relationship indicates that each model instance contains or owns one instance of the other model. For example, if each supplier has only one account, you can declare a supplier model like this:

class Supplier < ApplicationRecord

  has_one :account

end

Diagram for has_one connection

The corresponding migration might look like this:

class CreateSuppliers < ActiveRecord::Migration[5.0]

  def change

    create_table :suppliers do |t|

      t.string :name

      t.timestamps

    end

    create_table :accounts do |t|

      t.belongs_to :supplier, index: true

      t.string :account_number

      t.timestamps

    end

  end

end

Depending on the application, it may be necessary to create a unique index and/or foreign key constraint on the specified column in the accounts table. In this case, the column definition might look like this:

create_table :accounts do |t|

  t.belongs_to :supplier, index: { unique: true }, foreign_key: true

  # …

end

Leave a Reply

Your email address will not be published.