如果旧属性的整数值与新属性的整数值相同,则验证不起作用

Validation is not working if integer value of old attribute is same as that of new attribute

我有一个模型 Cartcart_itemshas_many 关系。

# cart.rb:
  accepts_nested_attributes_for :cart_items, allow_destroy: true
  has_many :cart_items, dependent: :destroy, inverse_of: :cart

# cart_item.rb:
  validates :quantity, presence: true, numericality: { greater_than: 0 }

# Controller code:

  def update
    current_cart.assign_attributes(params[:cart])
    .....
    current_cart.valid?
  end

更新 cart_items 时,如果数量的整数 (to_i) 值与旧值相同,则验证无效。

例如, 如果 quantity 的旧值为 4,现在新值更新为 4abc,则数量验证不起作用,记录被视为有效。

不过,如果新值从 4 更新为 5abc,则它会按预期显示验证错误。

对这一切发生的原因有什么建议吗?

编辑 1:

这是 rails 控制台的输出:

[3] pry(#<Shopping::CartsController>)> cart
=> #<Cart id: 12, created_at: "2017-06-22 13:52:59", updated_at: "2017-06-23 08:54:27">

[4] pry(#<Shopping::CartsController>)> cart.cart_items
[#<CartItem id: 34201, cart_id: 12, quantity: 4, created_at: "2017-06-23 05:25:39", updated_at: "2017-06-23 08:54:27">]

[5] pry(#<Shopping::CartsController>)> param_hash
=> {"cart_items_attributes"=>{"0"=>{"id"=>"34201", "quantity"=>"4abc"}}}
[6] pry(#<Shopping::CartsController>)> cart.assign_attributes param_hash
=> nil
[7] pry(#<Shopping::CartsController>)> cart.valid?
=> true

此处,cart_item 的先前数量是 4,新值是 4abc,但购物车仍然有效。

编辑 2:

我已经检查了 How to validate numericality and inclusion while still allowing attribute to be nil in some cases? 的答案,因为它在评论中被屏蔽为重复,但它似乎不起作用。

正如我上面提到的,如果 to_i 的新数量与之前的数量不同,验证工作正常,但如果相同则验证无效。

此外,我尝试使用 validate 应用自定义验证方法,但我在该方法中得到了 to_i 值。类似于:

型号代码:

validate :validate_quantity

# quantity saved in db => 4
# new quantity in form => 4abc
def validate_quantity
  puts quantity_changed? # => false
  puts quantity # => 4
end

编辑 3:

似乎如果 to_i 的新值与先前的值相同,则模型会将值转换为整数并考虑甚至未更新该字段。

编辑 4:

我收到了关于 to_i 目的的答案和评论。我知道 to_i 的作用。

我只想知道为什么 不是 如果 to_i 的新数量相似到数据库中存储的数量。

我知道 quantity 列是 integer 并且 ActiveRecord 会将其转换为整数但是必须存在验证错误,因为我已经在模型中添加了它。

I'm getting the validation error if to_i of new value is different than quantity stored in db.

但是

I'm not getting the validation error if to_i of new value is same than quantity stored in db.

to_i(p1 = v1) public

Returns 将 str 中的前导字符解释为整数基数(介于 2 和 36 之间)的结果。超出有效数字末尾的无关字符将被忽略。如果 str 的开头没有有效数字,则返回 0。当基数有效时,此方法从不引发异常。

"12345".to_i             #=> 12345
"99 red balloons".to_i   #=> 99
"0a".to_i                #=> 0
"0a".to_i(16)            #=> 10
"hello".to_i             #=> 0
"1100101".to_i(2)        #=> 101
"1100101".to_i(8)        #=> 294977
"1100101".to_i(10)       #=> 1100101
"1100101".to_i(16)       #=> 17826049

出现这个问题是因为

"4".to_i => 4
"4abc".to_i => 4

这意味着您的自定义验证将通过并且不会在页面上导致任何错误。

希望对你有所帮助...

由于 quantity 列定义为 integer,activerecord 在将其分配给属性之前将使用 .to_i 对其进行类型转换,但是,quantity_changed? 将在这种情况与上面显示的情况不同,因此这为您提供了解决方案 1 的提示,否则您可以像解决方案 2 一样检查参数是否仅包含整数或不包含在您的控制器中。

解决方案 1

validate :validate_quantity

# quantity saved in db => 4
# new quantity in form => '4abc'
def validate_quantity
  if quantity_changed?
    if quantity_was == quantity || quantity <= 0
      errors.add(:base, 'invalid quantity')
      return false
    else
      return true
    end
  else
    true
  end
end

解决方案 2

在你的控制器中,

before_action :validate_cart_params, only: [:create, :update]

private

def validate_cart_params
  unless cart_params[:quantity].scan(/\D/).empty?
    render json: { errors: "Oops! Quantity should be numeric and greater than 0." }, status: 422 and return
  end
end

更新

我用谷歌搜索了一下,但很晚才找到一个助手 _before_type_cast,这也是一个很好的解决方案,@Federico 已将其作为解决方案。我也将它添加到我的列表中。

解决方案 3

使用quantity_before_type_cast

validate :validate_quantity

# quantity saved in db => 4
# new quantity in form => '4abc'
def validate_quantity
  if quantity_changed? || ((actual_quantity = quantity_before_type_cast) != quantity_was)
    if actual_quantity.scan(/\D/).present? || quantity <= 0
      errors.add(:base, 'invalid quantity')
      return false
    else
      return true
    end
  else
    true
  end
end

先回答你的问题为什么

可能在历史上它的运作方式与今天不同。我个人怀疑这个提交(经过非常简短的搜索):https://github.com/rails/rails/commit/7500daec69499e4f2da2fc06cd816c754cf59504

如何修复

升级你的 rails gem...我可以推荐 Rails 5.0.3,我测试过它并且它按预期工作。

the source code for numeric validation之后,它在字符串上转换整数类型

"4abc".to_i
=> 4

因此输入大于0

要解决它,请尝试在您的视图中使用 <input type="numeric" /> 强制用户仅键入整数值

您应该使用 quantity_before_type_cast,如 here 部分所述,在 "Accessing attributes before they have been typecasted" 部分。例如:

validate :validate_quantity

# quantity saved in db => 4
# new quantity in form => 4abc
def validate_quantity
  q = quantity_before_type_cast
  return if q == q.to_i
  errors.add(:quantity, 'invalid quantity') unless (q.to_i.to_s == q)
end

此类问题已在 Github 中报告。

报告的问题:

修正于:

[v4.0]

[v3.2]

报告了所有问题,其中整数属性的先前值为 0 而新值为某个字符串(其 to_i 将为 0)。因此修复也仅适用于 0.

您可以在 #changes_from_zero_to_string? in active_record/attribute_methods/dirty.rb, which is initially called from #_field_changed?

中查看相同内容

解决方案:

仅针对 quantity 字段覆盖 #_field_changed?(由于时间不够。将来,我将覆盖所有整数字段的方法)。

现在,如果某些字母数字 quantityto_i 值等于数据库中的当前 quantity 值,则下面的方法将 return true 而不会键入值。

def _field_changed?(attr, old, value)
  if attr == 'quantity' && old == value.to_i && value != value.to_i.to_s
    return true
  end

  super
end