首页 > 解决方案 > How to store numeric values for weight / volume without getting float rounding errors and convert between units (g > kg)?

问题描述

I need to store in a Rails model called PackageType a volume and a weight. I have two (linked) problems:

  1. The values may not be integers. For example, it might be 0.5kg. But I want to avoid rounding errors linked to float: after adding / multiplying / dividing, I don't want to risk getting a 1.00000001kg (for accounting reasons mainly).
  2. I'm tempted for that to store them as a small unit where they'll always be integers: grams and cm3. (I know my packages won't be smaller than 1g / 1cm3). But that's not very user friendly: my users need to rather see and input the values in L (dm3) and in kg.

The solution I've found for now is to have columns weight_g and volume_cl:

rails g migration AddVolumeAndWeigthToPackageTypes volume_cm3:integer weight_g:integer

And have in my model some methods to convert between units:

class PackageType < ApplicationRecord
  …
  def volume_dm3
    volume_cm3.to_f / 1000
  end

  def volume_dm3=(vol)
    self.volume_cm3 = (vol*1000).to_i
  end

  def weight_kg
    weight_g.to_f / 1000
  end

  def weight_kg=(w)
    self.weight_g = (w*1000).to_i
  end
end

This works but doesn't feel right, and raises a few questions:

  1. Why in Rails Models can I access a Model attribute without the @ notation? Like I can read what's stored with just calling volume_cm3, and not self.volume_cm3 or @volume_cm3. And why can I read without putting self in front, but I need to precise self.volume_cm3 to write it?
  2. Is it the right way of doing it or should I just ditch this conversion and store directly kg and L as decimals? I'm not really expecting to display users in any other unit for now, kg and L are the only units I'll be using. I'm quite tempted to just use decimals, kg and L.
  3. When you need to store something that has a unit, what's the recommended way of naming your database columns and model attribute? Is it "volume" (unit being assumed), "volume_dm3", "volume_in_dm3"?

标签: ruby-on-railsruby-on-rails-6

解决方案


  1. The @ notation is to create an instance variable, so that variables from the controllers can be made accessible in the views. So the @ notation doesn't really have anything to do with accessing the variables within the model itself. As far as I know, the reason you need to specify self.volume_cm3 when writing to a variable and and can access it just using volume_cm3 is because when writing, Rails needs to know that you aren't creating a local variable with the same name as an attribute (I wouldn't recommend it, but I don't think ruby will stop you from doing so). So self.volume_cm3 = 3 will set the attribute volume_cm3, but volume_cm3 = 3 will just create a local variable with the value of 3.

  2. If you want to store the data as decimals, you can always round the decimals using '%.Nf' % volume_cm3 where N is the number of digits you want to round the decimal to. So '%.2f' % 1.345 would round to two decimals and give you 1.35.

  3. I don't think ruby has a specific naming guide when it comes to numbers and units, so it's probably just a matter of preference. If you have other units you will measure volume with, then it may be helpful to add the specific units to the end of the attribute name to avoid confusion (eg. volume_cm3, volume_L, etc). If you are only measuring in one unit per attribute, you can just name them volume and weight and just have some commentary in the model explaining that they need to be in specific units.


推荐阅读