Few months ago I wrote a blog post about ActiveRecord
before_validation callback and how it is used for wrong reasons and concluded that in most cases this is not something we should be using routinely. However, I missed one appropriate use case for it which might be quite common in Rails apps, so this might be an excellent opportunity to get back to before_validation callback and show its other side.
Anatomy Of The Problem
Imagine that we have a
Payment model where we need to store
currency. However, for statistical purposes, we also want to store normalized amount in USD currency with exchange rate applied at the time of payment’s creation. As this is a significant part of our domain, we want to add validation for
amount_in_usd attribute. Our Payment model looks like this at the moment:
class Payment < ApplicationRecord validates :amount, :currency, :amount_in_usd, presence :true end
The question is: where do we get
amount_in_usd from and how can we assign it?
One way of solving that problem would be a direct assignment when populating all the attributes. In that case, it would look a bit like this:
Payment.new(currency: currency, amount: amount, amount_in_usd: CurrencyExchanger.exchange(amount, from: currency, to: "USD"))
The problem with that solution is that this logic would need to be repeated in every place where payment gets initialized. We could implement a factory class that would be reused in all scenarios to keep it DRY, but that’s some extra overhead that is not popular in a Rails world. Also, this sounds like a responsibility of the Payment model itself as it is about managing its internal state.
Here, we can’t solve this by overriding writers as I suggested before as
amount_in_usd depends on two attributes:
amount, and we don’t know in which sequence the attributes will be assigned.
And this is exactly the case where
before_validation is useful: for complex state normalization where multiple attributes are involved. With that callback, a solution looks quite elegant and just simpler:
class Payment < ApplicationRecord validates :amount, :currency, :amount_in_usd, presence :true before_validation :assign_amount_in_usd private def assign_amount_in_usd if currency && amount self.amount_in_usd = CurrencyExchanger.exchange(amount, from: currency, to: "USD") end end end
In the first paragraph, I mentioned that this solution could work especially well in Rails apps. What I meant by that is the fact that usually, the “primitive” attributes coming from HTTP params are mass-assigned to the model. Of course in Ruby, everything is an object, but to keep things simpler, let’s treat numeric types and strings as primitives.
What would be a non-primitive value though? In our case, we have something that is widely used as a typical example of a value object: Money object that is composed of
currency. If the attributes before the assignment were mapped to some more domain-oriented objects, we would have an even simpler solution for our problem:
money = Money.new(amount, currency) Payment.new(money: money)
and the model would look like this:
class Payment < ApplicationRecord validates :amount, :currency, :amount_in_usd, presence :true def money=(money_object) self.amount = money_object.amount self.currency = money_object.currency self.amount_in_usd = CurrencyExchanger.exchange_money(money_object, to: "USD") end end
It might look like extra overhead that is not necessary. However, value objects tend to simplify and DRY a lot of things in the code, so for more complex apps, using value objects will be worth that extra overhead.
There are some cases where
before_validation callback might be useful. However, in more complex apps, using value object might be an alternative worth looking into.