Live data from Hacker News

Rails, callbacks, workers, and the race you never expected to lose

logicalfriday.com

1–10 of 28 posts

Re: Rails, callbacks, workers, and the race you never expected to lose

#2
Good points over all. If you don't want to disable transaction fixtures for certain specs and still want to use after_commit, I have this little patch - https://gist.github.com/3205621 which works flawlessly.

And I liked author's solution of tracking attribute changes. But may be there is a cleaner way. I had similar problem where, I needed a handle on after_commit :on => :update but in observer.

Re: Rails, callbacks, workers, and the race you never expected to lose

#4
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.)

Re: Rails, callbacks, workers, and the race you never expected to lose

#5
post #4

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_sav…

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.

Re: Rails, callbacks, workers, and the race you never expected to lose

#6
This isn't actually an isolated Rails issue (although the example is perfectly explained with Rails).

To tell a quick little story, I have experienced this issue in a system on a very unpredictable way. It cost me hours (days?) of repeated bug hunting to figure out what was happening.

So, our users will save an "Entity". The entity is actually pretty complex, and has the potential to span 40-50 tables. (O how I wish I used a Document Store rather than relational). Anyway, when the "entity" gets saved, I need to update Solr search, with an updated reflection of the model. There are two flags within this model, "enabled" and "deleted", and I use these flags to filter on the Documents within Solr.

All jobs are sent to Solr by going through a Gearman Job Server worker process.. However, the worker, was receiving the job before the database had persisted the updated model, so it was always one snapshot before the user initialized the request.

But oddly enough, this issue would only ever happen when the server was under very very small load. I could not for the life of me track this issue down, and it took me months before I realised this was almost akin to a race condition, especially since when under load the bug never happened... And.. If I artificially added load, the bug wouldn't happen. It would only happen when the server was running smoothly.

Once I spotted this, I was able to fork the request off to Gearman at the correct time, (rather than as a prePersist lifecycle event that I was previously).

I definitely learnt a lot from this little issue. It was such a simple issue, that eluded me for months, and just goes to show, that not all code runs the same... given different conditions, (ie Load), you will find that your code can behave differently.

Re: Rails, callbacks, workers, and the race you never expected to lose

#8
post #4

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_sav…

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.

Re: Rails, callbacks, workers, and the race you never expected to lose

#10
post #4

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_sav…

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.

Post reply on HN