无效小数在 Rails 中变为 0.0
我有以下 Rails 模型:
class Product < ActiveRecord::Base
end
class CreateProducts < ActiveRecord::Migration
def self.up
create_table :products do |t|
t.decimal :price
t.timestamps
end
end
def self.down
drop_table :products
end
end
但是当我在 Rails 控制台中执行以下操作时:
ruby-1.9.2-p180 :001 > product = Product.new
=> #<Product id: nil, price: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :002 > product.price = 'a'
=> "a"
ruby-1.9.2-p180 :003 > product.save
=> true
ruby-1.9.2-p180 :004 > p product
#<Product id: 2, price: #<BigDecimal:39959f0,'0.0',9(9)>, created_at: "2011-05-18 02:48:10", updated_at: "2011-05-18 02:48:10">
=> #<Product id: 2, price: #<BigDecimal:3994ca8,'0.0',9(9)>, created_at: "2011-05-18 02:48:10", updated_at: "2011-05-18 02:48:10">
如您所见,我写了“a”,它在数据库中保存了 0.0。这是为什么?这特别烦人,因为它绕过了我的验证,例如:
class Product < ActiveRecord::Base
validates :price, :format => /\d\.\d/
end
I have the following rails model:
class Product < ActiveRecord::Base
end
class CreateProducts < ActiveRecord::Migration
def self.up
create_table :products do |t|
t.decimal :price
t.timestamps
end
end
def self.down
drop_table :products
end
end
But when I do the following in the rails console:
ruby-1.9.2-p180 :001 > product = Product.new
=> #<Product id: nil, price: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :002 > product.price = 'a'
=> "a"
ruby-1.9.2-p180 :003 > product.save
=> true
ruby-1.9.2-p180 :004 > p product
#<Product id: 2, price: #<BigDecimal:39959f0,'0.0',9(9)>, created_at: "2011-05-18 02:48:10", updated_at: "2011-05-18 02:48:10">
=> #<Product id: 2, price: #<BigDecimal:3994ca8,'0.0',9(9)>, created_at: "2011-05-18 02:48:10", updated_at: "2011-05-18 02:48:10">
As you can see, I wrote 'a' and it saved 0.0 in the database. Why is that? This is particularly annoying because it bypasses my validations e.g.:
class Product < ActiveRecord::Base
validates :price, :format => /\d\.\d/
end
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
如果你调用 to_f 的话,任何无效的东西都会被转换为 0.0
"a".to_f #==> 0.0
你需要在模型中使用验证来检查它
validates_numericality_of :price # 至少在 Rails 2 中我认为
我不知道按格式验证的作用,所以我无法帮助你,但尝试验证它是否是一个数字,正则表达式仅针对字符串进行检查,因此如果数据库是数字字段,则可能会弄乱
:格式用于电子邮件地址、登录名、姓名等内容,以检查非法字符和这样的
anything that is invalid gets cast to 0.0 if you call to_f on it
"a".to_f #=> 0.0
you would need to check it with validations in the model
validates_numericality_of :price # at least in rails 2 i think
i dont know what validating by format does, so i cant help you there, but try to validate that it is a number, RegExs are only checked against strings, so if the database is a number field it might be messing up
:format is for stuff like email addresses, logins, names, etc to check for illegeal characters and such
你需要重新审视一下你真正的问题是什么。 Rails 的一个功能是,字符串会自动转换为适当的十进制值或 0.0。
发生了什么
1) 您可以将任何内容存储到 ActiveRecord 字段中。然后将其转换为数据库的适当类型。
2) 您应该使用正确的验证来确保只存储有效的数据。存储0值有什么问题吗?如果没有,那么您不需要验证。
3) 您不必验证号码是否将存储在数据库中。由于您将 db 字段声明为小数字段,因此它将仅保存小数(如果您让该字段具有空值,则为 null)。
4) 您的验证是面向字符串的验证。因此,验证正则表达式将 0.0 BigDecimal 更改为“0.0”,并且它通过了您的验证。您认为为什么您的验证被绕过了?
5) 到底为什么您担心其他程序员将字符串存储到您的价格字段中?
您是否试图避免产品被错误地设置为零价格?有几种方法可以解决这个问题。您可以检查传入的值(在转换为小数之前)以查看其格式是否正确。请参阅 AR 部分“覆盖默认访问器”,
但我认为这会很混乱且容易出错。您必须从 Setter 中设置记录的 Error 对象,或者使用一个标志。并且简单的类检查不起作用,请记住表单数据始终以字符串形式出现。
推荐 相反,请让用户确认他们打算通过使用额外的 AR-only 字段(未存储在 dbms 中的字段)将产品的价格设置为 0。
例如
注释以上内容是包含在测试中非常重要的内容。
另外我过去也遇到过类似的情况。根据我的经验,我现在在数据库中记录了说该值确实应该为 $0(或负数)的人的姓名,并让他们有一个 255 个字符的原因字段来证明其理由。当人们想知道原因是什么时,可以节省很多时间。
You need to re-look at what is your real issue is. It is a feature of Rails that a string is auto-magically converted into either the appropriate decimal value or into 0.0 otherwise.
What's happening
1) You can store anything into an ActiveRecord field. It is then converted into the appropriate type for database.
2) You should use the correct validation to make sure that only valid data is stored. Is there anything wrong with storing the value 0? If not, then you don't need a validation.
3) You don't have to validate that a number will be stored in the database. Since you declared the db field to be a decimal field, it will ONLY hold decimals (or null if you let the field have null values).
4) Your validation was a string-oriented validation. So the validation regexp changed the 0.0 BigDecimal into "0.0" and it passed your validation. Why do you think that your validation was bypassed?
5) Why, exactly, are you worried about other programmers storing strings into your price field?
Are you trying to avoid products being set to zero price by mistake? There are a couple of ways around that. You could check the value as it comes in (before it is converted to a decimal) to see if its format is right. See AR Section "Overwriting default accessors"
But I think that would be messy and error prone. You'd have to set the record's Error obj from a Setter, or use a flag. And simple class checking wouldn't work, remember that form data always comes in as a string.
Recommended Instead, make the user confirm that they meant to set the price to 0 for the product by using an additional AR-only field (a field that is not stored in the dbms).
Eg
Notes The above is the sort of thing that is VERY important to include in your tests.
Also I've had similar situations in the past. As a result of my experiences, I now record, in the database, the name of the person who said that the value should indeed be $0 (or negative) and let them have a 255 char reason field for their justification. Saves a lot of time later on when people are wondering what was the reason.