ruby-on-rails - 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:
- 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).
- 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:
- 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?
- 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.
- 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"?
解决方案
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 usingvolume_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). Soself.volume_cm3 = 3
will set the attributevolume_cm3
, butvolume_cm3 = 3
will just create a local variable with the value of 3.If you want to store the data as decimals, you can always round the decimals using
'%.Nf' % volume_cm3
whereN
is the number of digits you want to round the decimal to. So'%.2f' % 1.345
would round to two decimals and give you1.35
.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 themvolume
andweight
and just have some commentary in the model explaining that they need to be in specific units.
推荐阅读
- python - 在 django 模板中渲染具有打开/关闭可能性的 MPTT 结构
- java - Java XML 编写
- python - Python和pandas,DataFrame中的groupby唯一列
- html - 导航栏下拉菜单显示在错误的位置
- go - Golang 索引一个字符串数组
- php - 用户名在php中没有回显
- docker - NGINX:“proxy_set_header”中的参数数量无效
- python - 为数据帧的每一行计算新值的熊猫方法是什么?
- python-3.x - python3 图形。如何向 user_id 发送消息
- unity3d - Unity Baked Global Illumination 动态对象很暗(即使使用光探头)(URP)