如果旧属性的整数值与新属性的整数值相同,则验证不起作用
Validation is not working if integer value of old attribute is same as that of new attribute
我有一个模型 Cart
与 cart_items
有 has_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
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 中报告。
报告的问题:
- Parent model not marked as invalid when associated record is invalid, but not dirty
- Validations not triggered when attribute is changed from 0 to a string if it's a nested attribute
- Validates_numericality_of skipped when changing 0 to "foo" through accepts_nested_attributes_for
修正于:
[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?
(由于时间不够。将来,我将覆盖所有整数字段的方法)。
现在,如果某些字母数字 quantity
的 to_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
我有一个模型 Cart
与 cart_items
有 has_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 thanquantity
stored in db.
但是
I'm not getting the validation error if
to_i
of new value is same thanquantity
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
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 中报告。
报告的问题:
- Parent model not marked as invalid when associated record is invalid, but not dirty
- Validations not triggered when attribute is changed from 0 to a string if it's a nested attribute
- Validates_numericality_of skipped when changing 0 to "foo" through accepts_nested_attributes_for
修正于:
[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?
(由于时间不够。将来,我将覆盖所有整数字段的方法)。
现在,如果某些字母数字 quantity
的 to_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