Saving values to the db as metric but displaying/editing in imperial.

I have a requirement to store values in a database as unitless decimals(12,9) which will be presumed to be metric values. For display/editing purposes I need the user to be able to choose their input method – no mixing and matching on a form, just a simple User#current_units call to determine the expected units.

For example.

item:

name: item1

diameter: 150

width: 6

When User#current_units is “mm” all of my form fields are straight forward…

f.label “Diameter”

f.text_field :diameter

My issue is when a current_units is “in”, what’s a good method for doing all the translation? The text_field for diameter should read “5.9055”. If a user altered it to read “6” then it should save back to the db as “152.400000000”

I’ve tried everything from the ruby-units gem to composed_of (which is practically useless with rails 4 getting rid of it). storing the units in the db is a no go only because I need to be able to search for “items where diameter > x and diameter < y”

If anyone has any theories on a good starting point I’d love to elaborate further on what I’ve tried and failed with.

Thanks,

  • FJM

Unless I'm missing something, you want a setter and getter for a "virtual" attribute: say DiameterStr and DiameterStr=, which perform or not the conversion as needed. Then you always reference f.text_field :DiameterStr.

You could always play around with making the underlying attributes private, or at least a diameter= method which throws an exception... (Which slightly complicates DiameterStr=...)

Thanks Scott,

I’m closer but not quite there. I had originally tried virtual attributes but all my implementations fell short. After you suggested it, I did some more homework and tried again. Now I have a solution that works in every way except validation. My virtual attributes are defined like this:

def diameter_in_units
Unit.to_current(read_attribute(:diameter))
end

def diameter_in_units=(value)
write_attribute(:diameter, Unit.to_db(value))
end

#to_current and #to_db just apply a conversion factor depending on the users current_units setting but my issue is visible when the user errantly inputs something that isn’t a number, it never triggers my validations. I’m not 100% but I believe it is because the #to_ methods are coercing the string input to a BigDecimal before doing any calculation and therefore if the user inputs a string it helpfully changes it to zero for the calculation.

1.9.3p194 :036 > “randomstring”.to_d.to_s
=> “0.0”

How can I get around this without duplicating validation code while still getting the full “validation experience”?

Thanks for your advice,

  • FJM

In case anyone ever wants to do something similar and runs into the same follies I’ll leave my solution here:

Model

I never saw your second question; but I like your solution and will use it :wink: