Before Type Cast
While working on Kludge, I ran into a bit of a gotcha that I thought I’d share with everyone.
In our invoicing system, we have a Invoice model that has many LineItems. These line items, have a quantity attribute, and a quantity_type. This way, your line item can say “10 hours”, or “5 products”. When storing a quantity of hours, we wanted to actually store minutes (like 150), and then do some work on the reader when accessing that value (like 1.5) only when the quantity_type was :hour.
The Code
We thought it would be best to run a simple before_validation that checks whether or not the quantity being saved was to be saved in hours, and if so, convert it to minutes. Something like this:
1 2 3 4 5 6 | class LineItemTest < ActiveSupport::TestCase def test_quantities_change_based_on_quantity_type_when_read @li = LineItem.create(:quantity => 0.5, :quantity_type => :hour) assert_equal(30, @li.read_attribute(:quantity)) end end |
Our before validation looks like this:
1 2 3 4 5 6 7 8 9 10 11 | class LineItem < ActiveRecord::Base before_validation :convert_quantity private def convert_quantity logger.warn("-----#{quantity}") self.quantity = (quantity_type.to_sym == :hour) ? (quantity * 60).to_i : quantity end end |
This code was failing, and looking at our logs showed something strange:
1 2 | -----0 SQL (0.4ms) INSERT INTO "line_items" ("created_at", "description", "invoice_id", "price_in_cents", "quantity", "quantity_type", "updated_at") VALUES ('2010-11-13 19:04:25.813989', NULL, NULL, NULL, 0, 'hour', '2010-11-13 19:04:25.813989') |
Note the “—–0″: that zero is the value we are getting for quantity, before we even run any code. So what’s wrong?
Typecasting and *_before_type_cast
As it turns out, ActiveRecord will typecast your data before you get to it, and since our quantity column is an integer, the value of 0.5 was cast into 0. To correct this error, you can use the dynamic method *_before_type_cast to get the value …. well …. before type casting.
Updating the validation to:
1 2 3 4 | private def convert_quantity self.quantity = (quantity_type.to_sym == :hour) ? (quantity_before_type_cast * 60).to_i : quantity end |
… gave us our passing results. Hope this helps!