如何验证一个字段是空的或者是一个数字?

huangapple go评论80阅读模式
英文:

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

问题

我想验证接收到的参数是空字符串还是正数。我尝试过以下代码:

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

但它不起作用。我仍然可以输入非空字符串和负数值。我该如何做?

英文:

I want to validate if a received param is either an empty string or a positive number. I tried with:

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

but it doesn't work. I can still either put non-empty strings and negative values. How should I do it?

答案1

得分: 1

TL;DR

Ruby on Rails 3 is really old, and while you may get an answer that works on Rails 3, it's long past end-of-life and so are the versions of Ruby that it ran on. My answer will assume Ruby 3.2.2 and Rails 7.0.4.2, but you can certainly adapt it to older versions to the extent that the features you want are supported or are willing to roll your own compatible implementations.

Preface: User Input is Always a String

In Ruby on Rails, ActiveRecord validations of the sort you're using are generally a model concern. However, user input from HTML forms is always a String unless unless your model, view, or controller is coercing it before passing it onto some other part of the application.

There are a lot of different places in your application that you might put your logic, but best practice is generally to put such logic in the model. ActiveRecord takes care of a lot of the coercions for you, which makes it the simplest and most reliable solution here. There may be reasons to do additional validations or coercions elsewhere in the MVC stack too, so I'll try to give at least some light coverage to those options as well.

Best Practice: Model Validations

Model: Using ActiveRecord Validations

If you follow the historical best practice of "fat model, skinny controller" then you can do one or more validations within your model. An example might include running multiple validations. You can do this with one variable validation per line, or use a list of validations on the same line.

Note that many validations also accept a Hash of keyword parameters for additional checks. Consider the following, which uses the :allow_blank and :numericality validations in recent versions of Rails.

class Foo < ApplicationRecord
  validates :my_field,
    allow_blank: true,
    numericality: {
      only_integer: true,                                                       
      greater_than_or_equal_to: 1
    }   
end

You can test this within rails console like so:

Foo.create(my_field: "").save!
#=> true

Foo.create(my_field: nil).save!
#=> true

Foo.create(my_field: "1").save!
#=> true

The :allow_blank validation also accepts a literal nil value in addition to an empty String. Some databases such as SQLite will not coerce the nil into a String for you; results can vary with other databases, although many will raise an exception if the field's type definition doesn't allow null.

If you want to ensure you don't store a literal nil value in a database-agnostic way, then you'll either need to exclude nils by setting the :allow_nil Hash value to false in the validation above, or add a #before_save callback to coerce the nil into an appropriate String value before saving it to the database.


With the bang methods, a failed validation will raise:

> Validation failed: My field is not a number (ActiveRecord::RecordInvalid)

You can adjust the various validations and make changes to the messages returned or exceptions raised if needed, but chances are that this will be enough to get you going, and will suffice for your immediate needs.

Other Options

View: Use Constraints on Your Form

You could do something like this in your view or partial:

<input type="number" name="my_field" min="1"/>

This would ensure that one couldn't enter a value into the form of less than one. There are lots of other HTML and JavaScript validations you could do, but ensuring that users can't enter a negative or non-integer value in the first place is a good start. It will still be a String when the controller gets the response, but at least it will already be in a coercible format.

Controller: Coerce String to Integer

In your controller, you can also take values returned by the user and coerce them before calling ActiveRecord methods on the values. I'm not 100% sure that recent versions of Rails don't do some of this automatically for you based on the field type, but if they don't you can simply use some really basic Ruby. For example:

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

This approach won't guarantee that the user put in a valid value. It just ensures that the value is either zero or a positive integer. You could also ensure that it's never less than the minimum value with something like:

@my_field = params[:my_field].to_i.abs
@my_field = @my_field.nonzero? ? @my_field : @my_field.succ

This solves both problems, but doesn't actually prevent the user from entering invalid data or not entering anything at all. It just ensures the variable holds the right sort of data before you call any model methods.

英文:

TL;DR

Ruby on Rails 3 is really old, and while you may get an answer that works on Rails 3, it's long past end-of-life and so are the versions of Ruby that it ran on. My answer will assume Ruby 3.2.2 and Rails 7.0.4.2, but you can certainly adapt it to older versions to the extent that the features you want are supported or are willing to roll your own compatible implementations.

Preface: User Input is Always a String

In Ruby on Rails, ActiveRecord validations of the sort you're using are generally a model concern. However, user input from HTML forms is always a String unless unless your model, view, or controller is coercing it before passing it onto some other part of the application.

There are a lot of different places in your application that you might put your logic, but best practice is generally to put such logic in the model. ActiveRecord takes care of a lot of the coercions for you, which makes it the simplest and most reliable solution here. There may be reasons to do additional validations or coercions elsewhere in the MVC stack too, so I'll try to give at least some light coverage to those options as well.

Best Practice: Model Validations

Model: Using ActiveRecord Validations

If you follow the historical best practice of "fat model, skinny controller" then you can do one or more validations within your model. An example might include running multiple validations. You can do this with one variable validation per line, or use a list of validations on the same line.

Note that many validations also accept a Hash of keyword parameters for additional checks. Consider the following, which uses the :allow_blank and :numericality validations in recent versions of Rails.

class Foo < ApplicationRecord
  validates :my_field,
    allow_blank: true,
    numericality: {
      only_integer: true,                                                       
      greater_than_or_equal_to: 1
    }   
end

You can test this within rails console like so:

Foo.create(my_field: "").save!
#=> true

Foo.create(my_field: nil).save!
#=> true

Foo.create(my_field: "1").save!
#=> true

The :allow_blank validation also accepts a literal nil value in addition to an empty String. Some databases such as SQLite will not coerce the nil into a String for you; results can vary with other databases, although many will raise an exception if the field's type definition doesn't allow null.

If you want to ensure you don't store a literal nil value in a database-agnostic way, then you'll either need to exclude nils by setting the :allow_nil Hash value to false in the validation above, or add a #before_save callback to coerce the nil into an appropriate String value before saving it to the database.


With the bang methods, a failed validation will raise:

> Validation failed: My field is not a number (ActiveRecord::RecordInvalid)

You can adjust the various validations and make changes to the messages returned or exceptions raised if needed, but chances are that this will be enough to get you going, and will suffice for your immediate needs.

Other Options

View: Use Constraints on Your Form

You could do something like this in your view or partial:

<input type="number" name="my_field" min="1"/>

This would ensure that one couldn't enter a value into the form of less than one. There are lots of other HTML and JavaScript validations you could do, but ensuring that users can't enter a negative or non-integer value in the first place is a good start. It will still be a String when the controller gets the response, but at least it will already be in a coercible format.

Controller: Coerce String to Integer

In your controller, you can also take values returned by the user and coerce them before calling ActiveRecord methods on the values. I'm not 100% sure that recent versions of Rails don't do some of this automatically for you based on the field type, but if they don't you can simply use some really basic Ruby. For example:

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

This approach won't guarantee that the user put in a valid value. It just ensures that the value is either zero or a positive integer. You could also ensure that it's never less than the minimum value with something like:

@my_field = params[:my_field].to_i.abs
@my_field = @my_field.nonzero? ? @my_field : @my_field.succ

This solves both problems, but doesn't actually prevent the user from entering invalid data or not entering anything at all. It just ensures the variable holds the right sort of data before you call any model methods.

huangapple
  • 本文由 发表于 2023年4月6日 19:19:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/75948921.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定