Active Record 数据验证

数据验证概览

为什么要做数据验证

数据验证确保只有有效的数据才能存入数据库,在模型中做验证是最有保障的,只有通过验证的数据才能存入数据库。数据验证和使用的数据库种类无关,终端用户也无法跳过,而且容易测试和维护。

数据验证的方式主要有数据库原生约束、客户端验证和控制器层验证:

  • 数据库约束无法兼容多种数据库,难以测试和维护,但是如果其他应用也要使用这个数据库,最好能够在数据库层做一些约束。

  • 客户端验证可靠性不高,但是和其他验证方式结合可以提供实时反馈

  • 控制器层验证不灵便,难以测试和维护,只要可能就应该保证控制器的代码简洁,这样才有利于长远发展

Active Record 对象分为两种,一种在数据库中有对应记录,一种没有,新建对象还不属于数据库,只有调用了 save 方法后,才会存入数据库,可以使用 new_record? 方法判断是否存入数据库,未存入则返回 true ,存入则返回 false

新建并保存会执行 SQL INSERT 操作,更新记录会执行 SQL UPDATE 操作,一般情况下,数据验证发生在执行这些SQL语句之前,如果验证失败,对象会被标记为无效, Active Record 不会向数据库发送指令。

以下方法会触发数据验证:

  • create

  • create!

  • save

  • save!

  • update

  • update!

炸弹方法会在验证失败后抛出异常。

以下方法会跳过验证,不管验证是否通过都会把对象存入数据库:

  • decrement!

  • decrement_counter

  • increment!

  • increment_counter

  • toggle!

  • touch

  • update_all

  • update_attribute

  • update_column

  • update_columns

  • update_counters

同时,使用 save 方法时,如果传入 validate: false 参数,也会跳过验证。

同时,也可以使用 valid? 方法自己执行验证,如果对象上没有错误则返回 true ,否则返回 falseinvalid? 方法则相反。执行验证之后,错误可以通过实例方法 errors.message 获取,这个方法返回一个错误集合,如果为空,则说明对象是有效的。需要注意的是,如果没有验证数据,这个方法返回的也是一个空集合。

如果要验证某个属性是否有效,可以使用 errors[:attribute] ,这返回一个包含了所有错误的数组,如果没有错误则返回空数组,这个方法和 invalid? 方法不一样,这个方法不会验证整个对象,只会检查某个属性是否有错。

可以使用 errors.details[:attribute] 检查到底是哪个验证导致属性无效,这个方法返回一个由散列组成的数组。

数据验证的辅助方法

辅助方法可以直接在模型中使用,这些方法提供了常用的验证规则,验证失败就会向对象的 errors 集合中添加一个消息。

每个辅助方法都可以接受任意个属性名,所以一行代码可以在多个属性上做同一种验证。

acceptance

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

class Person < ApplicationRecord
   validates :terms_of_service, acceptance: true
end
复制代码
validates_associated

如果模型与其他模型有关联,而且关联的模型也需要验证,就是用这个方法,保存对象时,会在相关联的每个对象上调用 valid? 方法。

class Library < ApplicationRecord 
    has_many :books
    validates_associated :books
end
复制代码

不要在关联的两端使用,这样会造成无限的循环

confirmation

检查两个文本字段的值是否完全相同,如确认邮件地址或者密码。这个验证创建一个虚拟属性,其名字为要验证的属性名后加 _confirmation

class Person < ApplicationRecord
    validates :email, confirmation: true
end
复制代码

在视图模板中视图可以如下:

<%= 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

class Person < ApplicationRecord
    validates :email, confirmation: {case_sensitive: false}
end
复制代码
exclusion

这个方法检查属性的值是否不在指定的集合中,集合可以是任何一种可枚举的对象

class Account < Application
   validates :subdomain, exclusion: {in: %w(www us ca jp), message: "%{value} is reserved"}
end
复制代码

in 选项设置哪些值不能作为属性的值,in 的别名是 with

formate

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

class Product < ApplicationReocrd
    validates :legacy_code, formate: {with: /\A[a-zA-Z]+\z/, message: "only allows letters"}
end
复制代码
inclusion

这个方法检查属性的值是否在指定的集合中,集合可以是任何一种可枚举的对象

class Coffee < ApplicationRecord
    validates :size, inclusion: {in: %w(small mediun large), message: "%{value} is not a valid size"}
end
复制代码
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
复制代码

可用的长度约束选项有:

  • :minimum:最短长度

  • :maximum:最长长度

  • :in 或者 :within:长度范围

  • :is:等于该长度

定制错误消息可以使用 :wrong_length:too_long:too_short 选项,%{count} 表示长度限制的值

class Person < ApplicationRecord
    validates :bio, length: {maximum: 1000, too_long: "%{count} characters is the maximum allowed"}
end
复制代码
numericality

检查属性是否只包含数字,默认匹配的值是可选的正负符号后加整数或浮点数,如果只接受整数,把 :only_integer 选项设置为 true,否则会使用Float把值转换为数字。

class Player < ApplicationRecord
    validates :points, numericality: true
    validates :games_played< numericality: {only_integer: true}
end
复制代码

除此之外,这个方法还可指定以下选项:

  • :greater_than :属性值需大于 >

  • :greater_than_or_equal_to :>=

  • :equal_to :=

  • :less_than :<

  • :less_than_or_equal_to :<=

  • :other_than :!=

  • :odd :必须为奇数

  • :even :必须为偶数

此方法默认不接受 nil 值,可以使用 allow_nil: true 选项允许接受 nil

presence

检查属性是否为非空值,方法调用 blank? 方法检查是否为 nil 或空字符串

class Person < ApplicationRecord
   validates :name, :login, :email, presence: true
end
复制代码
absence

验证属性值是否为空,使用 present? 方法检查是否为 nil 或空字符串

class Person < ApplicationRecord
    validates :name, :login, :email, absence: true
end
复制代码
uniqueness

这个方法在保存对象前验证属性值是否唯一,这个方法不会在数据库中创建唯一性约束,所以有可能两次数据库连接创建的记录具有相同的值,所以最好在数据库字段上建立唯一性约束。

class Account < ApplicationRecord
    validates :email, uniqueness: true
end
复制代码

这个验证会在模型对应的表中执行一个 SQL 查询,检查现有的记录中该字段是否已经出现过相同的值。

可以使用 :case_sensitive 选项

class Person < ApplicationRecord
    validates :name, uniqueness: {case_sensitive: false}
end
复制代码
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
复制代码

这个方法的参数是一个类或者一组类。

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
复制代码

代码块的参数是记录、属性名和属性值。

常用验证选项

:allow_nil

允许 nil 值,如果要验证的值是 nil 就跳过验证

class Coffee < ApplicationRecord
    validates :size, inclusion: {in: %w(small medium large), message: "%{value} is not a valid size"}, allow_nil: true
end
复制代码
:allow_blank

与上面方法类似,使用 blank? 方法判断,空字符串和nil时跳过验证

:message

添加错误消息,消息中可以包含 %{value}%{attribute}%{model}

:on

指定验证时机,默认都在保存时验证,使用使用

  • on: :create :只在创建时验证

  • on: :update:只在更新时验证

class Person < ApplicationRecord
  # 更新时允许电子邮件地址重复
  validates :email, uniqueness: true, on: :create
 
  # 创建记录时允许年龄不是数字
  validates :age, numericality: true, on: :update
 
  # 默认行为(创建和更新时都验证)
  validates :name, presence: true
end
复制代码
:strict

使用严格验证模式,对象无效时抛出异常


class Person < ApplicationRecord
  validates :name, presence: { strict: true }
end
 
Person.new.valid?  # => ActiveModel::StrictValidationFailed: Name can't be blank

复制代码
条件验证

使用 :if:unless 选项只有满足特定条件才验证,值可以是符号、字符串、Proc或数组。 选项为符号时,表示验证之前执行对应的方法。这是最常用的设置方法。

class Order < ApplicationRecord
    validates :card_number
end
复制代码

自定义验证

自定义验证类继承自 ActiveModel::Validator,必须实现validate方法,参数是要验证的记录


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::Errors 的实例包含所有的错误,键是每个属性的名称,只是一个数组,包含错误消息字符串。

errors[] 用于获取某个属性上的错误消息

errors.add 用于手动添加某属性的错误消息,参数是属性和错误消息

errors.details 返回错误详情

errors.clear 清楚errors集合中的所有消息

errors.size 返回错误消息总数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值