Pros and cons of using callbacks for domain logic in Rails - ruby-on-rails

What do you see as the pros and cons of using callbacks for domain logic? (I'm talking in the context of Rails and/or Ruby projects.)
To start the discussion, I wanted to mention this quote from the Mongoid page on callbacks:
Using callbacks for domain logic is a bad design practice, and can lead to
unexpected errors that are hard to debug when callbacks in the chain halt
execution. It is our recommendation to only use them for cross-cutting
concerns, like queueing up background jobs.
I would be interested to hear the argument or defense behind this claim. Is it intended to apply only to Mongo-backed applications? Or it is intended to apply across database technologies?
It would seem that The Ruby on Rails Guide to ActiveRecord Validations and Callbacks might disagree, at least when it comes to relational databases. Take this example:
class Order < ActiveRecord::Base
before_save :normalize_card_number, :if => :paid_with_card?
end
In my opinion, this is a perfect example of a simple callback that implements domain logic. It seems quick and effective. If I was to take the Mongoid advice, where would this logic go instead?

I really like using callbacks for small classes. I find it makes a class very readable, e.g. something like
before_save :ensure_values_are_calculated_correctly
before_save :down_case_titles
before_save :update_cache
It is immediately clear what is happening.
I even find this testable; I can test that the methods themselves work, and I can test each callback separately.
I strongly believe that callbacks in a class should only be used for aspects that belong to the class. If you want to trigger events on save, e.g. sending a mail if an object is in a certain state, or logging, I would use an Observer. This respects the single responsibility principle.
Callbacks
The advantage of callbacks:
everything is in one place, so that makes it easy
very readable code
The disadvantage of callbacks:
since everything is one place, it is easy to break the single responsibility principle
could make for heavy classes
what happens if one callback fails? does it still follow the chain? Hint: make sure your callbacks never fail, or otherwise set the state of the model to invalid.
Observers
The advantage of Observers
very clean code, you could make several observers for the same class, each doing a different thing
execution of observers is not coupled
The disadvantage of observers
at first it could be weird how behaviour is triggered (look in the observer!)
Conclusion
So in short:
use callbacks for the simple, model-related stuff (calculated values, default values, validations)
use observers for more cross-cutting behaviour (e.g. sending mail, propagating state, ...)
And as always: all advice has to be taken with a grain of salt. But in my experience Observers scale really well (and are also little known).
Hope this helps.

EDIT: I have combined my answers on the recommendations of some people here.
Summary
Based on some reading and thinking, I have come to some (tentative) statements of what I believe:
The statement "Using callbacks for domain logic is a bad design practice" is false, as written. It overstates the point. Callbacks can be good place for domain logic, used appropriately. The question should not be if domain model logic should go in callbacks, it is what kind of domain logic makes sense to go in.
The statement "Using callbacks for domain logic ... can lead to unexpected errors that are hard to debug when callbacks in the chain halt execution" is true.
Yes, callbacks can cause chain reactions that affect other objects. To the degree that this is not testable, this is a problem.
Yes, you should be able to test your business logic without having to save an object to the database.
If one object's callbacks get too bloated for your sensibilities, there are alternative designs to consider, including (a) observers or (b) helper classes. These can cleanly handle multi object operations.
The advice "to only use [callbacks] for cross-cutting concerns, like queueing up background jobs" is intriguing but overstated. (I reviewed cross-cutting concerns to see if I was perhaps overlooking something.)
I also want to share some of my reactions to blog posts I've read that talk about this issue:
Reactions to "ActiveRecord's Callbacks Ruined My Life"
Mathias Meyer's 2010 post, ActiveRecord's Callbacks Ruined My Life, offers one perspective. He writes:
Whenever I started adding validations and callbacks to a model in a Rails application [...] It just felt wrong. It felt like I'm adding code that shouldn't be there, that makes everything a lot more complicated, and turns explicit into implicit code.
I find this last claim "turns explicit into implicit code" to be, well, an unfair expectation. We're talking about Rails here, right?! So much of the value add is about Rails doing things "magically" e.g. without the developer having to do it explicitly. Doesn't it seem strange to enjoy the fruits of Rails and yet critique implicit code?
Code that is only being run depending on the persistence state of an object.
I agree that this sounds unsavory.
Code that is being hard to test, because you need to save an object to test parts of your business logic.
Yes, this makes testing slow and difficult.
So, in summary, I think Mathias adds some interesting fuel to the fire, though I don't find all of it compelling.
Reactions to "Crazy, Heretical, and Awesome: The Way I Write Rails Apps"
In James Golick's 2010 post, Crazy, Heretical, and Awesome: The Way I Write Rails Apps, he writes:
Also, coupling all of your business logic to your persistence objects can have weird side-effects. In our application, when something is created, an after_create callback generates an entry in the logs, which are used to produce the activity feed. What if I want to create an object without logging — say, in the console? I can't. Saving and logging are married forever and for all eternity.
Later, he gets to the root of it:
The solution is actually pretty simple. A simplified explanation of the problem is that we violated the Single Responsibility Principle. So, we're going to use standard object oriented techniques to separate the concerns of our model logic.
I really appreciate that he moderates his advice by telling you when it applies and when it does not:
The truth is that in a simple application, obese persistence objects might never hurt. It's when things get a little more complicated than CRUD operations that these things start to pile up and become pain points.

This question right here ( Ignore the validation failures in rspec ) is an excellent reason why to not put logic in your callbacks: Testability.
Your code can have a tendency to develop many dependencies over time, where you start adding unless Rails.test? into your methods.
I recommend only keeping formatting logic in your before_validation callback, and moving things that touch multiple classes out into a Service object.
So in your case, I would move the normalize_card_number to a before_validation, and then you can validate that the card number is normalized.
But if you needed to go off and create a PaymentProfile somewhere, I would do that in another service workflow object:
class CreatesCustomer
def create(new_customer_object)
return new_customer_object unless new_customer_object.valid?
ActiveRecord::Base.transaction do
new_customer_object.save!
PaymentProfile.create!(new_customer_object)
end
new_customer_object
end
end
You could then easily test certain conditions, such as if it is not-valid, if the save doesn't happen, or if the payment gateway throws an exception.

In my opinion, the best scenario for using callbacks is when the method firing it up has nothing to do with what's executed in the callback itself. For example, a good before_save :do_something should not execute code related to saving. It's more like how an Observer should work.
People tend to use callbacks only to DRY their code. It's not bad, but can lead to complicated and hard to maintain code, because reading the save method does not tell you all it does if you don't notice a callback is called. I think it is important to explicit code (especially in Ruby and Rails, where so much magic happens).
Everything related to saving should be be in the save method. If, for example, the callback is to be sure that the user is authenticated, which has no relation to saving, then it is a good callback scenario.

Avdi Grimm have some great examples in his book Object On Rails.
You will find here and here why he do not choose the callback option and how you can get rid of this simply by overriding the corresponding ActiveRecord method.
In your case you will end up with something like :
class Order < ActiveRecord::Base
def save(*)
normalize_card_number if paid_with_card?
super
end
private
def normalize_card_number
#do something and assign self.card_number = "XXX"
end
end
[UPDATE after your comment "this is still callback"]
When we are speaking of callbacks for domain logic, I understand ActiveRecord callbacks, please correct me if you think the quote from Mongoid referer to something else, if there is a "callback design" somewhere I did not find it.
I think ActiveRecord callbacks are, for the most (entire?) part nothing more than syntactic sugar you can rid of by my previous example.
First, I agree that this callbacks method hide the logic behind them : for someone who is not familiar with ActiveRecord, he will have to learn it to understand the code, with the version above, it is easily understandable and testable.
Which could be worst with the ActiveRecord callbacks his their "common usage" or the "decoupling feeling" they can produce. The callback version may seems nice at first but as you will add more callbacks, it will be more difficult to understand your code (in which order are they loaded, which one may stop the execution flow, etc...) and test it (your domain logic is coupled with ActiveRecord persistence logic).
When I read my example below, I feel bad about this code, it's smell. I believe you probably do not end up with this code if you were doing TDD/BDD and, if you forget about ActiveRecord, I think you would simply have written the card_number= method. I hope this example is good enough to not directly choose the callback option and think about design first.
About the quote from MongoId I'm wondering why they advice to not use callback for domain logic but to use it to queueing background job. I think queueing background job could be part of the domain logic and may sometimes be better designed with something else than a callback (let's say an Observer).
Finally, there is some criticism about how ActiveRecord is used / implemented with Rail from an Object Oriented programming design point of view, this answer contain good information about it and you will find more easily. You may also want to check the datamapper design pattern / ruby implementation project which could be replacement (but how much better) for ActiveRecord and do not have his weakness.

I don't think the answer is all too complicated.
If you're intending to build a system with deterministic behavior, callbacks that deal with data-related things such as normalization are OK, callbacks that deal with business logic such as sending confirmation emails are not OK.
OOP was popularized with emergent behavior as a best practice1, and in my experience Rails seems to agree. Many people, including the guy who introduced MVC, think this causes unnecessary pain for applications where runtime behavior is deterministic and well known ahead of time.
If you agree with the practice of OO emergent behavior, then the active record pattern of coupling behavior to your data object graph isn't such a big deal. If (like me) you see/have felt the pain of understanding, debugging and modifying such emergent systems, you will want to do everything you can to make the behavior more deterministic.
Now, how does one design OO systems with the right balance of loose coupling and deterministic behavior? If you know the answer, write a book, I'll buy it! DCI, Domain-driven design, and more generally the GoF patterns are a start :-)
http://www.artima.com/articles/dci_vision.html, "Where did we go wrong?". Not a primary source, but consistent with my general understanding and subjective experience of in-the-wild assumptions.

Related

Why before_save considered to be bad?

I am sorry if similar question have been asked already I couldn't find anything identical.
Thus, can someone tell me why before_save especially conditional one can to be considered bad, please?
before_save :something, if: Proc.new { self.abc == 'hello' }
Therefore I understand why validation sometimes fits much better, however what I don't understand is why some people think that callbacks can to be a bad thing to use and they force you to write only validations but never make them conditional.
I personally think that there's can be far larger problem because this change can affect already existing entries and so it's okay to implement conditional validator or to provide if for before_save if you plan to modify data only in certain case. Why some people think it's not okay? Could someone help me with that?
Thank you very much!
I think that the only disadvantage of before_save or before_validation is when it is not understood or used properly
That it should be used only after carefully deliberating that such callbacks are really meant to be use global-wise for all records (consider also old records that are already stored in the DB)
That if the callbacks are only applicable to certain specific records
or conditions, then it is better to not pollute the model, and just implement the logic outside the model.
That if the callbacks are changing a state, may it be the record or other records, then the names of those callbacks should be explicitly saying so, and that the developer should know and understood that these should not have inadvertent effects.
That if the callbacks are not changing state, then it is immediately safe to use as it guarantees immutability and idempotence.
That the order of the callbacks are important, and that limited understanding of what's happening may leave a developer / new developer to write code with inadvertent effects, and might not work all the time.
That before_save and before_validation are different, and that a developer should understood that some callbacks are meant to be used as before_save, and some, as before_validation
Aside from these, I do not see any problem with before_save as it can potentially clean and break down the logic of the code, and allows reusability especially if you have subclasses of the model.
Just my immediate thoughts on the matter...
Using callbacks are standard Rails practice! When used appropriately they are great DRY helpers when it comes to maintaining data-integrity. Callbacks are heavily used for data-formatting user input (like removing spaces or dashes from a cellphone number field) where responding back with an error via a validation would just frustrate the user. Use validations to handle cases you can't predict or data that would be otherwise unpredictable and callbacks elsewhere (like downcasing an email before saving).

Unit Testing Approach for an Algorithm in Rails model

I am yet to jump into the TDD/BDD group. Trying to make the mental switch. For now I have been writing the business logic before my tests.
In one of my Rails model, I have a complex algorithm implemented. The implementation can be thought of as a couple of nested loops with lot of method calls from the same model.
Most of these methods take a complex hash created initially in the loop, modify it and pass it on to another method further in the loop which then processes it till we arrive at the final answer hash.
How should I go around unit testing my methods?
Best practice would suggest that you test the boundaries rather than the internal method calls.
Testing the inner workings of a class tend to lead to brittle tests that break even though the end result is what you want.
In this regard it would be best to test the input against the expected output and avoid testing how it got to the output.
There is an excellent talk by Sandi Metz on the subject here http://vimeo.com/48106365

Pros and cons of database triggers vs Rails ActiveRecord callbacks?

I am writing a program using Ruby on Rails and PostgreSQL. The system generates alot of reports which are frequently updated and frequently accessed by users. I am torn between whether I should use Postgres triggers to create the report tables (like Oracle materialized views) or the Rails built in ActiveRecord callbacks. Has anyone got any thoughts or experiences on this?
Callback are useful in the following cases:
Combine all business logic in Rails models which ease maintainability.
Make use of existing Rails model code
Easy to debug
Ruby code is easier to be written/read than sql "maintainability"
Triggers are useful in the following cases:
Performance is a big concern. It is faster than callbacks.
If your concern is ease and clean then use callbacks. If your concern is performance then use triggers.
We had the same problem, and since this is an interesting topic, I'll elaborate based on our choice/experience.
I think the concept is more complex than what highlighted in the current answer.
Since we're talking about reports, I assume that the use case is updating of data warehousing tables - not a "generic" application (this assumption/distinction is crucial).
Firstly, the "easy to debug" idea is not [necessarily] true. In our case, it's actually counterproductive to think so.
In sufficiently complex applications, some types of callbacks (data warehousing updates/millions of lines of code/mid (or more) sized team) are simply impossible to maintain, because there are so many places/ways the database will be updated, that it will be practically impossible to debug missed callbacks.
Triggers don't have to be necessarily designed as the "complex and fast" logic.
Specifically, triggers may also works as low-level callback logic, therefore being simple and lean: they would simply forward the update events back to the rails code.
To wrap up, in the use case mentioned, rails callbacks should be avoided like the plague.
An efficient and effective design is to have RDBMS triggers adding records to a queue table, and a rails-side queueing system, which acts upon them.
(Since this post is old, I'm curious about which has been the experience of the OP)

Rails Observers - When to and when not to use observers in Rails

In an application that I'm working currently, I see lots of observers. This is indeed creating lot trouble for me while I make code changes, add new functionality, as these observers cause tons of side-effects.
I would like to know the occasions that demand an Observer and the ones people have experiences either empirical or personal on when one gets tempted to fall in the observer trap.
Your valuable experience, war-stories and thoughts are in demand. Please do shout out!
I feel that observers get a bad rap largely because people lump them in with ActiveRecord lifecycle callbacks as being the same thing. I do agree with a lot of the popular opinion on lifecycle callbacks being easy to misuse, getting yourself in a tangle, but I'm personally a big fan of observers for keeping things out of model classes that are not the particular model's core responsibility. Here's a hint: Rails' observers were partly inspired by aspect-oriented programming -- they're about cross-cutting concerns. If you're putting business logic in observers that is tightly coupled to the models they're observing, you're doing it wrong IMO.
They're ideal for keeping clutter out of model classes, like cache expiration (sweepers), notifications of various sorts, activity stream updates, kicking off background jobs to track custom analytics events, warm caches, etc.
I emphatically disagree with BlueFish about observers being difficult to properly unit test. This is precisely the biggest point that distinguishes them from lifecycle callbacks: you can test observers in isolation, and doing so discourages you from falling into many of the state- and order-heavy design pitfalls BlueFish refers to (which again I think is more often true of lifecycle callbacks).
Here's my prescription:
Disable all observers in your test suite by default. They should not be complicating your model tests because they should have separate concerns anyway. You don't need to unit test that observers actually fire, because ActiveRecord's test suite does that, and your integration tests will cover it. Use the block form of ActiveRecord::Base.observers.enable if you really believe there is a good reason to enable an observer for some small piece of your unit tests, but it's likely an indicator of misuse or a design problem.
Enable observers for your integration tests only. Integration tests of course should be full-stack and you should be verifying observer behavior in them like everything else.
Unit-test your observer classes in isolation (invoke the methods like after_create directly). If an observer isn't part of its observed model's business logic, it probably will not have much dependence on state details of the model instance, and shouldn't require much test setup. You can often mock collaborators here if you're reasonably confident that your integration tests cover what you most care about.
Here's my standard boilerplate spec/support/observers.rb for apps using RSpec:
RSpec.configure do |config|
# Assure we're testing models in isolation from Observer behavior. Enable
# them explicitly in a block if you need to integrate against an Observer --
# see the documentation for {ActiveModel::ObserverArray}.
config.before do
ActiveRecord::Base.observers.disable :all
end
# Integration tests are full-stack, lack of isolation is by design.
config.before(type: :feature) do
ActiveRecord::Base.observers.enable :all
end
end
And here is a real-world example that I hope illustrates a good case for using an observer, and testing it without pain.
IMHO - Observers Suck
I will go through a number of reasons why I think they do. Mind you this applies in general to the use of before_x or after_x methods as well - which are more piecemeal examples of the general Observer.
Makes it hard to write proper unit tests
Typically, when you write unit tests, you are testing a specific piece of functionality. To test an observer however, you need to 'trigger' the event in order to test it, and sometimes that is just downright inconvenient.
E.g. If you hook up an observer to before_save, then to trigger the code, you need to save the model. This makes it tricky to test, given that you might be testing business logic, not persistence. If you mock out the save, your trigger might not work. And if you let it save, then your tests are slow.
Requires state
Following on from the fact that observers tend to be hard to test, observers tend to also require a lot of state. The reason is because the logic in an observer is trying to distinguish between various 'business events' and the only way to do so is to look at the state of the object. This requires a lot of setup in your tests, and therefore makes testing hard, tedious and problematic.
Unintended consequences
No doubt, you have just experienced that because you can hook up multiple observations, you have no idea what could be triggering various behaviours. This leads to unintended consequences, one which you can only pick up through integration/system testing (slow feedback). Tracing your observers is also not a lot of fun.
Order assumptions problems
There is no guarantee when an observer may kick in. You are only guaranteed that it will be kicked off. If you have implicit order as part of your business rules, then Observers are wrong.
Leads to poor design
Adding things by hooking up observers leads to poor designs. It tends to lead you to hook everything up to save, delete, create events - which whilst convenient, is also hard to understand. E.g. saving a user could mean that you are updating the user's details, or it could mean that you are adding a new account name to it. Knowing what you can specifically do to an object is part of the reason why you have methods, and meaningful action-based names. If everything is an observer, then this gets lost and everything is now responding to events, and within your observation logic, you tend to try to distinguish the event, belonging to which business event.
There are some places where an observer is nice, but that is usually an exception. It is far better to engraft what can be done explicitly, rather than encode the logic implicitly via callbacks.
I partly agree with BlueFish, in that observers can introduce unnecessary complexity, however observers are useful for separating concerns from the object.
For example, in a Payment AR model one might want to deliver a receipt after create. Using a regular AR after_create callback, if the deliver_receipt method failed the payment wouldn't get written to the database - oops! However in an observer, the payment would still get saved.
One could argue that failures should be handled by rescue, but I still think it doesn't belong there; it belongs in an observer.

Business Logic Layer Pattern on Rails? MVCL

That is a broad question, and I appreciate no short/dumb asnwers like: "Oh that is the model job, this quest is retarded (period)"
PROBLEM Where I work at people created a system over 2 years for managing the manufacture process over demand in the most simplified still broad as possible, involving selling, buying, assemble, The system is coded over Ruby On Rails.
The app has been changed lots of times and the result is a mess on callbacks (some are called several times), 200+ models, and fat controllers: Total bad.
The QUESTION is, if there is a gem, or pattern designed to handle Rails large app logic? The logic whould be able to fully talk to models (whose only concern would be data format handling and validation)
What I EXPECT is to reduce complexity from various controllers, and hard to track callbacks into files with the responsibility to handle a business operation logic. In some cases there is the need to wait for a response, in others, only validation of the input is enough and a bg process would take place.
ie:
--> Sell some products (need to wait the operation to finish)
1. Set a View able to get the products input
2. Controller gets the product list inputed by employee and call the logic
Logic::ExecuteWithResponse('sell', 'products', :prods => #product_list_with_qtt, :when => #date, :employee => current_user() )
This Logic would handle buying order, assemble order, machine schedule, warehouse reservation, and others.
Have in mind that a callback on SalesOrder is not enough, since it depends on where it is called (no field for that), depends on the class of the user, among other stuff not visible for the model, or in some cases it would take long for the model to process.
The inherent complexity of the business object and logic will need to be tackled with, understood, and internalized (or at least documented well) for the team. That will not go away. The recommendation I have is to grab all the logic peppered throughout the "fat" controllers and move them into either the domain objects, application services (service layer), or simply transaction scripts (see Martin Fowler's "Patterns of Enterprise Application Architecture").
Ideally all the business logic embedded in the labyrinth of callbacks can be refactored into components described above to promote understanding. This gets rid of all the incidental complexity built up over time on the controllers. But even after getting rid of all that, I suspect a certain level of inherent complexity will remain in the problem domain.
The idea of Service Layer is to incorporate high level logic which is not associated with certain model in a good way. If you develop enterprise like system with multiple services integrated and have number of data sources you should better look to Domain-Driven Design (DDD) by Eric Evans. Surely, Fowler's Enterprise patterns book is good in this case too.
Also look at DataMapper(2, the first one was similar to ActiveRecord). It has better design approach for such type of systems and have less limitation (on conceptual level) than ActiveRecord in Rails.
Truly say, that's because of dynamic nature of Ruby people so long tackling conceptual problems of ActiveRecord and try to fit it enterprise needs, IMHO.
Some people have done some work out there on service layers and callbacks Pat Maddox (especially callbacks) is one, Jay Fields (his very early works around the rails presenter pattern later to be replaced with a service layer like pattern) is another. I must admit I like the idea of adding a extra layer. To me, business logic just doesn't belong in models and models should be decoupled in complex projects. I also like the idea of an extra layer over callbacks, for me callbacks become too complex as there numbers increase
This is far from full blown DDD and at the moment I don't know how DDD would work in Rails, however ,I am sure it could and I am sure someone is working on it out there right now. For my projects, at the moment, it would be a bit of a overkill to implement, however, I would consider adding a service layer.

Resources