Callbacks

Description

ActiveRecord callbacks like before_destroy or after_create can be used to add behavior that automatically runs during the lifecycle of a model instance. Callbacks are somewhat controversial in the Rails community. Depending on the nature of the behavior, they can make code harder to understand and surprising, especially when used excessively.

Technology Status at Wunder

Assess. Callbacks are often the simplest solution, but overuse can be problematic.

Notes/Resources

Callbacks are intended for database activity. E.g., the code in after_create will run inside the transaction, but before it’s committed to the database. If some other piece of data needs to be updated simultaneously, this can be the best time to do it.

  • Operations should be limited to database operations.
    • Avoid using callbacks for business logic that isn’t database related, e.g. sending an email, or hitting an external API.
  • Callbacks should be used when the behavior should always be run.
    • If the logic is instead placed in an actor, it can be difficult to ensure that every code path always uses the actor. If not using the actor would result in an invalid database state, using a callback can be more foolproof.
    • Note that most *_all methods do not run callbacks.
  • Avoid actors that only replicate ActiveRecord functionality.
    • E.g., Destroy actors that replicate the dependent: :destroy association behavior. Doing this ejects the model from the standard ActiveRecord lifecycle, making it only safe to destroy the model with that actor. That means that any other models can’t use dependent: :destroy on that association, and so on up the chain. It also creates two ways to destroy the model, and if only one of them is valid, it can lead to inconsistencies in the database.
    • Actors are for business logic, not to duplicate ActiveRecord functionality.
  • To avoid cross-segment callbacks, one option is to have the segment register its own callbacks via a concern. That can solve the encapsulation problem, where the other segment is responsible for its own behavior, and the model including the concern is unaware of it.