【问题标题】:Rails Validation numbericality fails on form object表单对象上的 Rails 验证编号失败
【发布时间】:2020-08-21 18:21:39
【问题描述】:

相关/固定:Ruby on Rails: Validations on Form Object are not working

我有以下验证..

validates :age, numericality: { greater_than_or_equal_to: 0, 
                                only_integer: true, 
                                :allow_blank => true
                              }

不是必须的,如果输入需要是一个数字。我注意到,如果有人输入单词而不是数字,则在提交并通过验证后,字段值会变为 0。我希望它为空白或输入的值。

更新:

仍然没有解决方案,但这里有更多信息。

rspec 测试
   it "returns error when age is not a number" do
      params[:age] = "string"
      profile = Registration::Profile.new(user, params)
      expect(profile.valid?).to eql false
      expect(profile.errors[:age]).to include("is not a number")
    end

Rspec 测试失败:

 Registration::Profile Validations when not a number returns error when age is not a number

 Failure/Error: expect(profile.errors[:age]).to include("is not a number")
   expected [] to include "is not a number"
2.6.5 :011 > p=Registration::Profile.new(User.first,{age:"string"})

2.6.5 :013 > p.profile.attributes_before_type_cast["age"]
=> "string" 

2.6.5 :014 > p.age
=> 0

2.6.5 :015 > p.errors[:age]
=> [] 

2.6.5 :016 > p.valid?
=> true 

#Form 对象注册:配置文件:

module Registration
  class Profile
    include ActiveModel::Model

    validates :age, numericality: { greater_than_or_equal_to: 0, 
                                    only_integer: true, 
                                    :allow_blank => true
                                   }
    attr_reader :user
    
    delegate :age , :age=, to: :profile
    
    def validate!
      raise ArgumentError, "user cant be nil" if @user.blank?
    end
    
    def persisted?
      false
    end
    
    def user
      @user ||= User.new
    end
    
    def teacher
       @teacher ||= user.build_teacher
     end
      
     def profile
       @profile ||= teacher.build_profile
     end
     
    def submit(params)
      profile.attributes = params.slice(:age)
      if valid?
        profile.save!
        true
      else
        false
      end
    end 
    def self.model_name
      ActiveModel::Name.new(self, nil, "User")
    end
    
    def initialize(user=nil, attributes={})
      validate!
      @user = user
    end
 end
end

#轮廓模型:

class Profile < ApplicationRecord
  belongs_to :profileable, polymorphic: true

  strip_commas_fields = %i[age]

  strip_commas_fields.each do |field|
    define_method("#{field}=".intern) do |value|
      value = value.gsub(/[\,]/, "") if value.is_a?(String) # remove ,
      self[field.intern] = value
    end
  end
end

有趣的是,如果将验证移至配置文件模型并检查 p.profile.errors,我会看到预期的结果,但看不到我的表单对象。我需要在我的表单对象上保留我的验证。

【问题讨论】:

  • 您可能在某处对值调用 to_i 并使用此结果而不是请求中传递的原始值 params
  • 没有验证不尝试转换值吗?
  • 验证完全不会改变分配的值。我的意思是您可能会在某个地方自己调用to_i(例如,在实际设置模型属性模型之前或在回调中)。
  • 但在我的情况下,价值正在改变。这就是整个问题,很奇怪
  • 显示注册/profile.rb

标签: ruby-on-rails ruby ruby-on-rails-6


【解决方案1】:

如果数据库中的基础列是数字类型,那么 Rails 会强制转换该值。我假设这是在 [ActiveRecord::Type::Integer#cast_value][1]

中完成的
def cast_value(value)
  value.to_i rescue nil
end

假设model 是一个ActiveRecord 模型,其中age 是一个整数列:

irb(main):008:0> model.age = "something"
=> "something"
irb(main):009:0> model.age
=> 0
irb(main):010:0>

这是因为提交表单总是会提交键值对,其中键值是字符串。 无论您的 DB 列是数字、布尔值、日期、...

它与验证本身无关。

您可以在类型转换之前访问该值,如下所示:

irb(main):012:0> model.attributes_before_type_cast["age"]
=> "something"

如果您的要求规定了另一种行为,您可以这样做:

def age_as_string=(value)
  @age_as_string = value
  self.age = value
end

def age_as_string
  @age_as_string
end

然后在您的表单(或其他)中使用age_as_string。您还可以为此属性添加验证,例如:

validates :age_as_string, format: {with: /\d+/, message: "Only numbers"}

您还可以添加自定义类型:

class StrictIntegerType < ActiveRecord::Type::Integer
  def cast(value)
    return super(value) if value.kind_of?(Numeric)
    return super(value) if value && value.match?(/\d+/)
  end
end

并通过“Attributes API”在您的 ActiveRecord 类中使用它:

attribute :age, :strict_integer

如果您尝试分配的值无效,这将保留age 属性nil

ActiveRecord::Type.register(:strict_integer, StrictIntegerType) [1]:https://github.com/rails/rails/blob/fbe2433be6e052a1acac63c7faf287c52ed3c5ba/activemodel/lib/active_model/type/integer.rb#L34

【讨论】:

  • 是的,活动记录,这似乎是正在发生的事情。如果使用 before_type_cast 返回原始值,我的模态会是什么样子?
  • 模态?你能解释一下吗?
  • 我的意思是模型,而不是模态
  • 好的,我明白了:-) attributes_before_type_cast["age"] 会让你访问它。但是你想达到什么目的呢?
  • @Pascal 谢谢,我最终添加了一个 JQuery 输入掩码
【解决方案2】:

为什么不在前端添加验证?您可以使用&lt;input type="number" /&gt; 而不是&lt;input type="text" /&gt;,它只接受用户的号码。我看到你解释这个问题的方式,这是一个需要在前端而不是后端解决的问题。

您可以在此处阅读更多信息:Number Type Input

如果这对您不起作用,请告诉我,我很乐意为您提供帮助。

【讨论】:

  • 我为输入添加了一个 JS 掩码。
  • 很高兴你解决了这个问题。恭喜!您可以添加您所做的作为答案吗?它可以帮助其他开发人员。干杯!
  • 前端验证可以帮助用户体验,但不应用作服务器端验证的替代品。数据可以通过其他方式进入您的系统(导入、rake 任务等)
  • 我会添加我的前端,但同意,后端仍然需要一个好的解决方案
猜你喜欢
  • 2015-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-20
相关资源
最近更新 更多