如何验证字段是空的还是数字?

How can I validate that a field is either empty or a number?

提问人:Andres Carretero 提问时间:4/6/2023 最后编辑:Todd A. JacobsAndres Carretero 更新时间:4/8/2023 访问量:187

问:

我想验证收到的参数是空字符串还是正数。我试过:

validates :my_field,
  presence: true,
  if: ->{
    ((my_field.is_a? String) && my_field.empty?) ||
    ((my_field.is_a? Numeric) && my_field > 0)
  }

但它不起作用。我仍然可以放置非空字符串和负值。我应该怎么做?

Ruby 字符串 验证 Ruby-on-Rails-3 数字

评论

3赞 spickermann 4/6/2023
数据库中属性的数据类型是什么?是否需要支持具有相同属性的两种类型?
1赞 Stefan 4/6/2023
“我应该怎么做?”– 混合字符串和数字似乎一开始就不对。你想达到什么目的?您能提供一些背景信息或举一些例子吗?根据模型的数据库架构,模型可能具有固定类型。由于 Rails 将类型转换任何值,因此您应该只为声明的类型编写验证。
3赞 Stefan 4/6/2023
顺便说一句,既然你标记了你的问题——那个版本的 Rails 已经有十多年的历史了,并在 2016 年达到了生命周期的终点。如果这是您的实际版本,您应该强烈考虑升级代码库。ruby-on-rails 3
0赞 tadman 4/6/2023
提示:而不是尝试帮助正确封装参数。(x.is_a? String)x.is_a?(String)
0赞 tadman 4/6/2023
您可能希望在使用 或 转换后验证该字段。不要对确切的类型挑剔,但要特别注意结果值。to_ito_f

答:

1赞 Todd A. Jacobs 4/8/2023 #1

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;结果可能因其他数据库而异,但如果字段的类型定义不允许,许多数据库会引发异常。nilnull

如果要确保不以与数据库无关的方式存储文本 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

这解决了这两个问题,但实际上并不能阻止用户输入无效数据或根本不输入任何内容。它只是确保变量在调用任何模型方法之前保存正确类型的数据。