Rails数据验证

数据验证

概览

会触发数据验证的方法:create, create!, save, save!, update, update!

会跳过验证,不管验证是否通过都会把对象存入数据库的方法:decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters

vaild?方法会触发数据验证,如果对象上没有错误,返回true,否则返回false

Active Record执行验证后,所有发现的错误都可以通过实例方法errors.messages获取。该方法返回一个错误合集。

class Person < ApplicationRecord
  validates :name, presence: true
end

p = Person.new
p.errors.messages
# => {}
p.valid?
# => false
p.errors.messages
# => {name:["can't be blank"]}

p = Person.create
p.errors.messages
# => {name:["can't be blank"]}
p.save
# => false

若想检查对象的某个属性是否有效,可以使用errors[:attribute]

class Person < ApplicationRecord
  validates :name, presence: true
end

Person.new.errors[:name].any? # => false
Person.create.errors[:name].any? # => true

若想查看是哪个验证导致属性无效的,可以使用 errors.details[:attribute]

数据验证辅助方法

每个辅助方法都可以接受任意个属性名,所以一行代码就能在多个属性上做同一种验证。
所以辅助方法都可以指定:on和:message选项,分别指定何时做验证,一级验证失败后向errors集合添加什么消息。

1.acceptance
这个方法检查表单提交时,用户界面中的复选框是否被选中。这个功能一般用来要求用户接受应用的服务条款、确保用户阅读了一些文本,等等。

class Person < ApplicationRecord
  vaildates :terms_of_service, acceptance: {message: 'must be abided', accept: ['yes', '1']} 
end

2.validates_associated
如果模型和其他模型有关联,而且关联的模型也要验证,要使用这个辅助方法。保存对象时,会在相关联的每个对象上调用 valid? 方法。
不要在关联的两端都使用 validates_associated,这样会变成无限循环。

3.confirmation
如果要检查两个文本字段的值是否完全相同,使用这个辅助方法。例如,确认电子邮件地址或密码。这个验证创建一个虚拟属性,其名字为要验证的属性名后加 _confirmation。
在试图模板中可以这么写:

<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>

只有 email_confirmation 的值不是 nil 时才会检查。所以要为确认属性加上存在性验证

class Person < ApplicationRecord
  validates :email, confirmation: true
  validates :email_confirmation, presence: true
end

此外,还可以使用 :case_sensitive 选项指定确认时是否区分大小写。这个选项的默认值是 true。

4.format
这个辅助方法检查属性的值是否匹配 :with 选项指定的正则表达式。

class Product < ApplicationRecord
  vaildates :legacy_code, format: { with: /\A[a-zA-Z]+/, message: 'only allow letters' }
end

或者,使用 :without 选项,指定属性的值不能匹配正则表达式。

5.inclusion、exclusion
inclusion检查属性的值是否在指定的集合中
exclusion检查属性的值是否不在指定的集合中

class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
    message: "%{value} is not a valid size" }
end

class Account < ApplicationRecord
  validates :subdomain, exclusion: { in: %w(www us ca jp),
    message: "%{value} is reserved." }
end

6.length
这个辅助方法验证属性值的长度,有多个选项,可以使用不同的方法指定长度约束

class Person < ApplicationRecord
  validates :name, length: { minimum: 2 }
  validates :bio, length: { maximum: 500 }
  validates :password, length: { in: 6..20 }
  validates :registration_number, length: { is: 6 }
end

7.numericality
这个辅助方法检查属性的值是否只包含数字。默认情况下,匹配的值是可选的正负符号后加整数或浮点数。如果只接受整数,把 :only_integer 选项设为 true。否则,会尝试使用 Float 把值转换成数字。

class Player < ApplicationRecord
  validates :points, numericality: true
  validates :games_played, numericality: { only_integer: true }
end

除了 :only_integer 之外,这个方法还可指定以下选项,限制可接受的值:

:greater_than:属性值必须比指定的值大。该选项默认的错误消息是“must be greater than %{count}”;
:greater_than_or_equal_to:属性值必须大于或等于指定的值。该选项默认的错误消息是“must be greater than or equal to %{count}”;
:equal_to:属性值必须等于指定的值。该选项默认的错误消息是“must be equal to %{count}”;
:less_than:属性值必须比指定的值小。该选项默认的错误消息是“must be less than %{count}”;
:less_than_or_equal_to:属性值必须小于或等于指定的值。该选项默认的错误消息是“must be less than or equal to %{count}”;
:other_than:属性值必须与指定的值不同。该选项默认的错误消息是“must be other than %{count}”。
:odd:如果设为 true,属性值必须是奇数。该选项默认的错误消息是“must be odd”;
:even:如果设为 true,属性值必须是偶数。该选项默认的错误消息是“must be even”;

numericality 默认不接受 nil 值。可以使用 allow_nil: true 选项允许接受 nil。

8.uniqueness
这个辅助方法在保存对象之前验证属性值是否是唯一的。该方法不会在数据库中创建唯一性约束,所以有可能两次数据库连接创建的记录具有相同的字段值。为了避免出现这种问题,必须在数据库的字段上建立唯一性索引。
:scope 选项用于指定检查唯一性时使用的一个或多个属性

class Holiday < ApplicationRecord
  validates :name, uniqueness: { scope: :year,
    message: "should happen once per year" }
end

如果想确保使用 :scope 选项的唯一性验证严格有效,必须在数据库中为多列创建唯一性索引。

9.validates_with
这个辅助方法把记录交给其他类做验证。

class GoodnessValidator < ActiveModel::Validator
  def validate(record)
    if record.first_name == 'Evil'
      record.errors[:base] << 'This person is evil'
    end
  end
end

class Person < ApplicationRecord
  validates_with GoodnessValidator
end

record.errors[:base] 中的错误针对整个对象,而不是特定的属性。

10.validates_each
这个辅助方法使用代码块中的代码验证属性。它没有预先定义验证函数,你要在代码块中定义验证方式。要验证的每个属性都会传入块中做验证。在下面的例子中,我们确保名和姓都不能以小写字母开头

class Person < ApplicationRecord
  validates_each :name, :surname do |record, attr, value|
    record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/
  end
end

条件验证

有时,只有满足特定条件时做验证才说得通。条件可通过 :if 和 :unless 选项指定,这两个选项的值可以是符号、字符串、Proc 或数组。:if 选项指定何时做验证。如果要指定何时不做验证,使用 :unless 选项。

1.使用符号
:if 和 :unless 选项的值为符号时,表示要在验证之前执行对应的方法。这是最常用的设置方法。

class Order < ApplicationRecord
  validates :card_number, presence: true, if: :paid_with_card?
 
  def paid_with_card?
    payment_type == "card"
  end
end

2.使用Proc
:if and :unless 选项的值还可以是 Proc。使用 Proc 对象可以在行间编写条件,不用定义额外的方法。这种形式最适合用在一行代码能表示的条件上。

class Account < ApplicationRecord
  validates :password, confirmation: true, unless: Proc.new { |a| a.password.blank? }
end

3.条件组合
有时,同一个条件会用在多个验证上,这时可以使用 with_options 方法

class User < ApplicationRecord
  with_options if: :is_admin? do |admin|
    admin.validates :password, length: { minimum: 10 }
    admin.validates :email, presence: true
  end
end

with_options 代码块中的所有验证都会使用 if: :is_admin? 这个条件。

4.联合条件
如果是否做某个验证要满足多个条件时,可以使用数组。而且,一个验证可以同时指定 :if 和 :unless 选项

class Computer < ApplicationRecord
  validates :mouse, presence: true,
                    if: ["market.retail?", :desktop?],
                    unless: Proc.new { |c| c.trackpad.present? }
end

只有当 :if 选项的所有条件都返回 true,且 :unless 选项中的条件返回 false 时才会做验证。

自定义验证

1.自定义验证类
自定义的验证类继承自 ActiveModel::Validator,必须实现 validate 方法,其参数是要验证的记录,然后验证这个记录是否有效。自定义的验证类通过 validates_with 方法调用。

class MyValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.starts_with? 'X'
      record.errors[:name] << 'Need a name starting with X please!'
    end
  end
end
 
class Person
  include ActiveModel::Validations
  validates_with MyValidator
end

在自定义的验证类中验证单个属性,最简单的方法是继承 ActiveModel::EachValidator 类。此时,自定义的验证类必须实现 validate_each 方法。这个方法接受三个参数:记录、属性名和属性值。它们分别对应模型实例、要验证的属性及其值。

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end
 
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end

2.自定义验证方法

class Invoice < ApplicationRecord
  validate :expiration_date_cannot_be_in_the_past,
    :discount_cannot_be_greater_than_total_value
 
  def expiration_date_cannot_be_in_the_past
    if expiration_date.present? && expiration_date < Date.today
      errors.add(:expiration_date, "can't be in the past")
    end
  end
 
  def discount_cannot_be_greater_than_total_value
    if discount > total_value
      errors.add(:discount, "can't be greater than total value")
    end
  end
end
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值