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!

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Twitter

Comment | Trackback


Uncategorized

Leave a Reply