如果旧属性的整数值与新属性

时间:2017-06-23 08:10:11

标签: ruby-on-rails ruby ruby-on-rails-3 validation activemodel

我的模型Carthas_manycart_items的关系。

# 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 console的输出:

[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:

我得到答案&amp;关于to_i的目的的评论。我知道to_i做了什么。

我只是想知道为什么我 如果新数量的to_i与数据库中存储的数量相似,则会收到验证错误。

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

  

如果新值的to_i与db中存储的quantity不同,我将收到验证错误。

BUT

  

如果新值to_i与db中存储的quantity相同,我 <\ n> 获取验证错误。

6 个答案:

答案 0 :(得分:0)

由于quantity列定义为integer,因此在将其分配给属性之前,activerecord会将其与.to_i进行类型转换,但是,quantity_changed?在这种情况下将为真与上面显示的不同,因此为您提供解决方案1的提示,否则您可以检查param是否仅在控制器中包含整数,如解决方案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

答案 1 :(得分:0)

首先回答您的问题为什么

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

如何修复那个?

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

答案 2 :(得分:0)

the source code for numeric validation之后,它在字符串

上强制转换Integer类型
ng-options

因此输入大于0

要解决此问题,请尝试在视图中使用"4abc".to_i => 4 强制用户仅键入整数值

答案 3 :(得分:0)

您应该使用quantity_before_type_cast,如here中所解释的那样,在&#34;在进行类型转换之前访问属性&#34;。例如:

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

答案 4 :(得分:0)

此类问题已在Github中报告。

报告的问题:

已修复:

[v4.0]

[v3.2]

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

您可以在active_record/attribute_methods/dirty.rb的{​​{3}}中查看相同内容,该quantity最初是从#changes_from_zero_to_string?调用的

解决方案:

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

现在,如果某个字母数字quantity的{​​{1}}值等于数据库中的当前quantity值,则下面的方法将返回true,并且不会输入值。< / p>

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

  super
end

答案 5 :(得分:-1)

to_i(p1 = v1) public

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

"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

这意味着您的自定义验证将通过,不会在页面上造成任何错误。

希望能帮到你......