In general are callbacks to be avoided if they can be?
In almost all cases, I try to avoid them. The exception is when I need to hook into the ActiveRecord lifecycle to do something that relates to persistence, like keeping a search index in sync or expiring cache keys. Another exceptions would be creating a password hash, for example.
There are a ton of reasons that I avoid them, but in general, they tend to take an important operation and obscure the fact that it is being done coincidentally. It’s just not immediately apparent what’s going on. This is important for other people, but also for you. I was tracking down why my tests were taking as long as they were and one of the culprits was that I had an observer (I tend to implement my callbacks in an observer, if I can) that was updating newsletter subscriptions in our newsletter system and another that was updating our analytics dashboard. Whenever I persisted a user in my tests, there were three database operations going on (one to persist the user, one to queue a job to update the subscription, and one to queue a job that updates the analytics dashboard).
I’ve also found that there are times when I don’t want observers to fire (e.g., updating a user via the admin dashboard versus a user updating their account).
Lastly, they are so easy to overuse that a policy of avoidance keeps them in check.
I recently tore all but one observer (the observer that keeps our search index in sync) and couldn’t be happier with what it did to the code. The only callback I retained that wasn’t in an observer was the one that hashes passwords.
The only benefit, as I see it, of callbacks is that if there is an operation that absolutely needs to be done every time and in every context, then the callback ensures that it happens, even if a developer forgets that it needs to happen.
Like most coding advice, the true answer is “it depends”. There are a few places where callbacks make sense. In general, I think you would be best served trying to avoid them. Let a small number into your codebase only where it’s so blindingly obvious that nothing but a callback will do.