Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

For this and related predictability/absence-of-surprise reasons, I have generally moved away from ActiveRecord callbacks in my Rails projects. It's very easy to forget that you e.g. have an after_save defined for accounts which made sense when accounts necessarily represented people with credit cards in the system but doesn't make sense for e.g. people paying via purchase order. It is also easy to forget an after_save which e.g. takes a consequential action like "purchases a phone number" which doesn't hurt when there is only one account.save in the code base but after you add the second one, say in a Rake task that executes every 5 minutes, blows up terribly.

These days if I find myself wishing I have a callback that probably means a related model method or controller needs to be one line longer than it is right now. (e.g. I moved the welcome email out of the after_save and into the controller in charge of online signups months ago. This was the right call, as it means that e.g. my magic administrator create-a-free-account-for-a-customer-who-pays-via-PO button doesn't actually mail them prior to their account being configured correctly.)



Agreed, I've come to view callbacks as a tool to enforce application-level data integrity and relationships, not a place that higher level business logic should live. The line is often fuzzy, but I think the question you alluded to, "should this happen with every conceivable create/save/etc.?" is a good general guideline.

One thing to keep in mind when moving logic out of callbacks is that you'll no longer get the implicit transaction that ActiveRecord wraps around the whole callback cycle, so you have to be a bit more hands-on with managing transactions.


And even if it happens for every create, the fact that the author had to do so much more work just to make something like this work is a sign that Model callbacks may not be the best solution.


Was going to say the same thing. Models should not know or care about background queues or mailings, and callback use has always caused problems for me later on.

Nothing against the OP, it does explain a common edge case that many people will run into and for that it's a good article, but I wish more people would see the violations of SRP going on here and try to lead people away from this trap.

I'd recommend watching http://www.youtube.com/watch?v=CGN4RFkhH2M (Hexagonal Rails GoRuCo talk) for one way to re-architect to get away from these inter dependencies.


Absolutely agreed - better to make these things explicit somewhere rather than having your business logic implicitly handled by something directly coupled to your persistence layer (with the exception of data integrity tasks as mentioned by danenania).

As a sort of half-way house between using lifecycle callbacks and sticking everything in the controller, I've just moved the main app I work on to a pub-sub event system, which is working out quite nicely. Controllers or mutator methods no longer handle post-action tasks, they just announce what they did, and any interested listeners can act upon the event announced. This way we can have a listener that handles (for example) all post-action emailing, and we can test the logic for that in isolation. Controller logic is cleaned up too, so testing them becomes easier. On the flip side we have to write more comprehensive integration tests to make sure our listeners are all hooked up correctly, but it seems like a pretty good trade-off so far.

Enjoyed the OP nonetheless, though - I didn't know about ActiveModel#previous_changes, that's pretty nifty to have.


I would love to read a write up on how you accomplished this.


I'll post something about it soon - while the changes were wide-ranging for us (because these post-action tasks were initially scattered throughout our app), the extra infrastructure required was really pretty minimal. Most of the actual time spent in this refactoring was writing new tests for code that we'd previously been unable to practically test.

In the meantime, the pub/sub setup we've got is pretty similar to the one presented a little way down the following article:

https://www.theagileplanner.com/blog/building-agile-planner/...

Our listeners sit completely separate from the controllers, though, and we're not using them for anything like handling controller response logic (I'm not sure how I feel about that, although the idea is certainly interesting).




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: