← Back

Active Record

In Active Record, objects carry both persistent data and behaviour which operates on that data. Active Record takes the opinion that ensuring data access logic as part of the object will educate users of that object on how to write to and read from the database.

Active Record as Object Relational Mapping

Object Relational Mapping, commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system. Using ORM, the properties and relationships of the objects in an application can be easily stored and retrieved from a database without writing SQL statements directly and with less overall database access code.

Naming Conventions

Rails will pluralize your class names to find the respective database table.

Model Class - Singular with the first letter of each word capitalized - BookClub Database Table - Plural with underscores separating words - book_clubs

Active Record Callbacks

Creating an Object

before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit / after_rollback

Updating an Object

before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
after_commit / after_rollback

Destroying an Object

before_destroy
around_destroy
after_destroy
after_commit / after_rollback

after_save runs both on create and update, but always after the more specific callbacks after_create and after_update, no matter the order in which the macro calls were executed.

Avoid updating or saving attributes in callbacks. For example, don't call update(attribute: "value") within a callback. This can alter the state of the model and may result in unexpected side effects during commit. Instead, you can safely assign values directly.

before_destroy callbacks should be placed before dependent: :destroy associations (or use the prepend: true option), to ensure they execute before the records are deleted by dependent: :destroy

As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.

The whole callback chain is wrapped in a transaction. If any callback raises an exception, the execution chain gets halted and a ROLLBACK is issued. To intentionally stop a chain use:

Any exception that is not ActiveRecord::Rollback or ActiveRecord::RecordInvalid will be re-raised by Rails after the callback chain is halted.

There are two additional callbacks that are triggered by the completion of a database transaction: after_commit and after_rollback. These callbacks are very similar to the after_save callback except that they don't execute until after database changes have either been committed or rolled back. They are most useful when your active record models need to interact with external systems which are not part of the database transaction.

When a transaction completes, the after_commit or after_rollback callbacks are called for all models created, updated, or destroyed within that transaction. However, if an exception is raised within one of these callbacks, the exception will bubble up and any remaining after_commit or after_rollback methods will not be executed. As such, if your callback code could raise an exception, you'll need to rescue it and handle it within the callback to allow other callbacks to run.

The code executed within after_commit or after_rollback callbacks is itself not enclosed within a transaction.

Using both after_create_commit and after_update_commit with the same method name will only allow the last callback defined to take effect, as they both internally alias to after_commit which overrides previously defined callbacks with the same method name.

hasOne vs belongsTo

Depends on where the foreign key is present. Product belongs_to Shop means products table has shop_id.

has_many :through

  1. When both models has_many associations with each other and it requires a another model through which to make associations.

  2. This is also useful for setting up "shortcuts" through nested has_many associations. has_one :through can be used like this.

Single Table Inheritance

In Single-Table Inheritance (STI), many subclasses inherit from one superclass with all the data in the same table in the database. The superclass has a “type” column to determine which subclass an object belongs to.

Polymorphic Association

In a polymorphic association, one model “belongs to” several other models using a single association. Each model, including the polymorphic model, has its own table in the database.

Consider how the application might change and grow. If the structure is likely to remain the same for all future models and does not deviate from the shared structure, STI will generally be faster for querying.

STI have more power than just forming a link. Foreign keys also prevent referential errors by requiring that the object referenced in the foreign table does, in fact, exist.

Unfortunately, polymorphic classes can’t have foreign keys. Type and id columns in place of a foreign key. This means it loses the protection that foreign keys offer.

Rails and ActiveRecord help out on the surface, but anyone with direct access to the database can create or update objects that reference null objects.

One should only use polymorphic associations when your database is contained. If other applications or databases need to access it, you should consider other methods.

Transactions in ActiveRecord

ActiveRecord::Base.transaction do
  sender.debit_account(amount) if sender.sufficient_balance(amount)
  credit_amount = convert_currency(amount, recipient)
  perform_transfer(recipient, credit_amount, sender)
  transfer.update_status
end

Every database operation that happens inside that block will be sent to the database as a transaction. If any kind of unhandled error happens inside the block, the transaction will be aborted, and no changes will be made to the DB.

Performance - Using a transaction will consume more resources on the DB server than the raw queries.

For example, when you use a transaction in Rails, it ties up one of your DB connections until all the code in your transaction block finishes running. If the block contains something slow, like an API call, you could tie up a DB connection for an unreasonable amount of time.

Both #save and #destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks will happen under its protected cover.

As a consequence changes to the database are not seen outside your connection until the operation is complete. For example, if you try to update the index of a search engine in after_save the indexer won't see the updated record. The after_commit callback is the only one that is triggered once the update is committed. See below.