提问人:Andres Carretero 提问时间:4/6/2023 最后编辑:Todd A. JacobsAndres Carretero 更新时间:4/8/2023 访问量:187
如何验证字段是空的还是数字?
How can I validate that a field is either empty or a number?
问:
我想验证收到的参数是空字符串还是正数。我试过:
validates :my_field,
presence: true,
if: ->{
((my_field.is_a? String) && my_field.empty?) ||
((my_field.is_a? Numeric) && my_field > 0)
}
但它不起作用。我仍然可以放置非空字符串和负值。我应该怎么做?
答:
TL;博士
Ruby on Rails 3 真的很老了,虽然你可能会得到一个适用于 Rails 3 的答案,但它早已过了生命周期的尽头,运行它的 Ruby 版本也是如此。我的答案是假设 Ruby 3.2.2 和 Rails 7.0.4.2,但你当然可以把它调整到旧版本,只要你想要的功能得到支持,或者愿意推出你自己的兼容实现。
前言:用户输入始终是字符串
在Ruby on Rails中,您正在使用的ActiveRecord验证通常是一个模型问题。但是,来自 HTML 表单的用户输入始终是 String,除非您的模型、视图或控制器在将其传递到应用程序的其他部分之前对其进行强制。
应用程序中有很多不同的位置可以放置逻辑,但最佳做法通常是将此类逻辑放在模型中。ActiveRecord为您处理了很多强制问题,这使其成为最简单,最可靠的解决方案。可能也有理由在 MVC 堆栈的其他地方进行额外的验证或强制,因此我将尝试至少对这些选项进行一些轻微的覆盖。
最佳实践:模型验证
模型:使用 ActiveRecord 验证
如果遵循“胖模型,瘦控制器”的历史最佳实践,则可以在模型中执行一个或多个验证。例如,运行多个验证。为此,可以使用每行一个变量验证,也可以在同一行上使用验证列表。
请注意,许多验证还接受关键字参数的哈希值以进行其他检查。请考虑以下情况,它使用最新版本的 Rails 中的 :allow_blank 和 :numericality 验证。
class Foo < ApplicationRecord
validates :my_field,
allow_blank: true,
numericality: {
only_integer: true,
greater_than_or_equal_to: 1
}
end
你可以像这样测试它:rails console
Foo.create(my_field: "").save!
#=> true
Foo.create(my_field: nil).save!
#=> true
Foo.create(my_field: "1").save!
#=> true
侧边栏:为数据库适当地处理 Nil 值
除了空 String 之外,:allow_blank 验证还接受文本值。某些数据库(例如SQLite)不会将nil强制转换为String;结果可能因其他数据库而异,但如果字段的类型定义不允许,许多数据库会引发异常。nil
null
如果要确保不以与数据库无关的方式存储文本 nil 值,则需要通过在上面的验证中将 :allow_nil Hash 值设置为来排除 nil,或者添加 #before_save 回调以将 nil 强制转换为适当的 String 值,然后再将其保存到数据库。false
使用 bang 方法时,失败的验证将引发:
验证失败:我的字段不是数字 (ActiveRecord::RecordInvalid)
如果需要,您可以调整各种验证并更改返回的消息或引发的异常,但这很可能足以让您继续前进,并且足以满足您的即时需求。
其他选项
视图:在窗体上使用约束
你可以在你的视图中或部分地做这样的事情:
<input type="number" name="my_field" min="1"/>
这将确保不能以小于 1 的形式输入值。您可以执行许多其他 HTML 和 JavaScript 验证,但首先确保用户不能输入负数或非整数值是一个好的开始。当控制器获得响应时,它仍然是一个字符串,但至少它已经是强制格式。
控制器:将字符串强制转换为整数
在控制器中,还可以获取用户返回的值,并在对值调用 ActiveRecord 方法之前强制这些值。我不是 100% 确定最新版本的 Rails 不会根据字段类型自动为您完成一些工作,但如果它们没有,您可以简单地使用一些非常基本的 Ruby。例如:
class MyController < ApplicationController
def create
# Coerce the value, even if #blank?, to an
# Integer. Note that if the param is #blank?
# then #to_i will return +0+. #abs ensures
# the value is positive.
@my_field = params[:my_field].to_i.abs
end
此方法不能保证用户输入有效的值。它只是确保该值为零或正整数。您还可以使用如下方式确保它永远不会小于最小值:
@my_field = params[:my_field].to_i.abs
@my_field = @my_field.nonzero? ? @my_field : @my_field.succ
这解决了这两个问题,但实际上并不能阻止用户输入无效数据或根本不输入任何内容。它只是确保变量在调用任何模型方法之前保存正确类型的数据。
评论
ruby-on-rails 3
(x.is_a? String)
x.is_a?(String)
to_i
to_f