侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

Rails 学习笔记 03: Active Record 数据验证

barwe
2021-09-15 / 0 评论 / 0 点赞 / 455 阅读 / 6,693 字
温馨提示:
本文最后更新于 2022-07-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Active Record 数据验证

数据验证简介

为什么要做数据验证?

数据从客户端向数据库流动的各个环节均可以进行数据验证,但是各有优劣之处。

客户端验证

客户端的验证更多的是为用户提供了实时反馈,提示用户修改数据。用作安全性验证时,客户端验证并不可靠,例如通过禁用 JavaScript 可以跳过浏览器的验证。

控制器验证

控制器的设计目的是接受并处理数据,它虽然可以同时进行数据验证,但过多的验证会使逻辑变得混乱、代码变得混乱。控制器可能会将数据分发到各个模型进行保存,多个控制器也可能调用相同的模型操纵数据,此时控制器层面上的数据验证就会变得复杂冗余。

模型验证

Rails 中期望的数据验证方式,在定义模型时定义数据验证方式,数据验证直接绑定到模型的各个属性上。只有通过验证的数据才能存入数据库,数据验证能够与数据库的类型解绑。基于模型的验证最常用。

数据库的内建约束

依赖于数据库的类型,维护困难,适用情景较少,一般不推荐使用。

什么时候做数据验证?

数据对象的两种状态:已经存入数据库中、只在内存中尚未存入数据库。可以使用实例方法new_record?进行判断。

基于模型的数据验证发生在 INSERT 和 UPDATE 语句执行之前,用来判断是否持久化到数据库。

会进行验证的方法有create, updatesave以及它们的严格版本(带!后缀的方法)。在宽松方法下如果未通过验证,create会返回对象本身,update&save会返回false;在严格方法下如果未通过验证,会抛出ActiveRecord::RecordInvalid异常。

跳过验证

不管验证是否通过都会更新数据库的方法有:

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters
  • save(validate: false)

valid? 和 invalid?

实例方法valid?用来检查数据对象是否合法,它会触发数据验证。如果验证失败会将错误信息保存到实例属性.errors.message中。

errors.message只有在数据验证触发时才会更新,所以新建的数据对象的errors.message是一个空哈希,不管它合不合法。

errors.message会按属性名保存错误信息,通过实例属性.errors可以提取各个属性的报错信息。

errors[]

在数据验证之后,使用实例属性.errors[:attribute]来查看某属性相关的错误信息,没有错误时为空数组。

该方式只收集错误信息,不会触发数据验证

数据验证帮助方法

Rails 提供了大量可直接使用的验证方法。

validate

acceptance

略。

validates_associated

验证关联模型,会在每个关联对象上依次调用valid?方法。

class Library < ActiveRecord::Base
    has_many :books
    validates_associated :books
end

如果两个模型相互关联,只能在一个模型上定义validates_associated,否则会陷入死循环。

错误消息会保存到每个关联对象的errors.message中,而不是集中在当前对象上。

confirmation

略。

exclusion

检查属性的值是否不在指定的集合中。

class Account < ActiveRecord::Base
    validates :subdomain, exclusion: {
        in: %w[www us ca jp],
        message: "%{value} is reserved"
    }
end

exclusion选项中的:in可以换成:with:message中使用%{value}获取属性的值。

format

正则匹配。

class Product < ActiveRecord::Base
    validates :legacy_code, format: {
    	with: /\A[a-zA-Z]+\z/,
        message: "only allow letters"
    }
end

inclusion

exclusion相似,检查属性的值是否在指定的集合中,使用:in或者:within

length

验证属性值的长度。可选的选项有

  • :minimum
  • :maximum
  • :in or :within:指定范围
  • :is

定制消息也有很多可用的模板变量,此处略。

numericality

是否只包含数值。选项有

  • :only_integer: bool, 需为整数
  • :greater_than: number, 需比指定值大
  • :greater_than_or_equal_to
  • :equal_to
  • :less_than
  • :less_than_or_equal_to
  • :odd: bool, 需为奇数
  • :even: bool, 需为偶数

presence

:presence设为true保证属性值不为nil或者空字符串。该方法只能用来验证非布尔类型的属性。

如果测试的是关联的对象,则需要关联的对象存在:

class LineItem < ActiveRecord::Base
    belongs_to :order
    validates :orders, presence: true
end

同时关联模型上需要使用:inverse_of进行声明:

class Order < ActiveRecord::Base
    has_many :line_items, inverse_of: :order
end

验证布尔类型的属性值是否存在需要使用inclusion:

class Order < ActiveRecord::Base
    validates :done, inclusion: {in: [true, false]}

uniqueness

不是很安全的唯一性约束,检查数据表中是否已经存在该属性值。但是当多个连接存入同一个值时,数据表中还是可能会出现相同的值,要保证绝对的唯一,需要在数据库层面设置唯一性约束。

:scope选项指定一个字段,用作检验的范围。在指定字段值的子表中检验是否已经存在该值,此时数据表中可能会存在多个相同的属性值。

:case_sensitive指定是否大小写敏感,默认为true

class Holiday < ActiveRecord::Base
    validates :email, uniqueness: true
    validates :name, uniqueness: {
        scope: :year,
        case_sensitive: false,
        message: "should happen once per year"
    }
end

validates_with

使用自定义的数据验证类数据对象进行验证。验证类需继承ActiveModel::Validator基类,实现其validate方法,并将错误信息写入errors中。validate方法形式如下:

class GoodnessValidator < ActiveModel::Validator
    def validate(record)
        if options[:fields].any? { |field| record.send(field) == "Evil" }
        	record.errors[:base] << "This person is evil"
        end
    end
end

class Person < ActiveRecord::Base
    validates_with GoodnessValidator, fields: %w[first_name last_name]
end
  • validate函数只有一个参数record,表示数据对象
  • validate的其他参数保存在options

验证类在整个程序生存周期中只初始化一次,在validate方法中使用实例对象时最好是在构造函数中显示声明:

class GoodnessValidator < ActiveModel::Validator    def initialize(person)        @person = person    end        def validate        # use `@person` as the datum instance instead of `record`    endend

validates_each

将多个属性传入代码块依次进行验证,格式为

class Person < ActiveRecord::Base    validates_each %w[name surname] do |record, attr, value|        # record 是数据对象,attr 是属性名称,value 是属性值        # 验证失败时将错误信息添加到 record.errors 中即可    endend

常用的验证选项

:allow_nil

允许验证的值为nil

class Coffee < ActiveRecord::Base    validates :size, allow_nil: trueend

:allow_blank

允许验证的值为nil或者空字符串。

class Topic < ActiveRecord::Base    validates :title, length: {is: 5}, allow_blank: trueend

:message

略。

:on

在何时进行数据验证,内建的验证方法总是在数据创建和更新时自动进行数据验证,即调用save, create, update方法时。

:on可选值有:create:update

class Person < ActiveRecord::Base    validates :email, uniqueness: true, on: :create    validates :age, numericality: true, on: :update    validates :name, presence: true # both :create and :updateend

严格验证

在严格模式下,验证失败会抛出异常。
:strict选项设置为true启用严格模式。
默认抛出ActiveModel::StrictValidationFailed异常。

:strict选项指定为特定的异常类还可以抛出特定类型的异常。

class Person < ActiveRecord::Base    validates :token, presence: true, uniqueness: true, strict: TokenGenerationExceptionend

条件验证

满足特定的条件才进行验证,使用:if or :unless设置验证的前提条件。

指定 Symbol

:if or :unless的值为 Symbol 时,表示执行一个方法。

class Order < ActiveRecord::Base    validates :card_number, presence: true, if: :paid_with_card?        def paid_with_card?        payment_type == "card"    endend

指定字符串

:if or :unless的值为字符串时,该字符串会自动使用eval执行,因此该字符串必须是一串 Ruby 代码,适用于代码非常短时。

class Order < ActiveRecord::Base    validates :surname, presence: true, if: "name.nil?"end

指定 Proc

没看懂,略。

条件组合

使用with_options:if or :unless条件用在多个属性的验证上

class User < ActiveRecord::Base    with_options if: :is_admin? do |admin|        admin.validates :password, length: {minimu: 10}        admin.validates :email, presence: true    endend

联合条件

使用多个条件验证同一个属性。

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

自定义验证方式

自定义类进行验证

参考validates_with的用法,为整个数据对象指定一个验证类。

class MyValidator < ActiveModel::Validator    def validate(record)        # options        # record.errors    endendclass Person < ActiveRecord::Base    validates_with MyValidatorend

也可以为单个属性定义验证类,该类名称与属性对应,继承自ActiveModel::EachValidator基类,实现validate_each方法。在模型中使用与属性同名的选项启用属性验证类。

class EmailValidator < ActiveModel::EachValidator    def validate_each(record, attr, value)    endendclass Person < ActiveRecord::Base    validates :email, presence: true, email: trueend

自定义方法进行验证

可以将实例方法方法设为数据验证的方法,区分验证方法和普通方法的方式是:使用实例的validate方法注册哪些方法是用于数据验证的方法。在数据验证方法中,如果验证失败,只需将错误信息保存到实例的errors属性中即可。

class Invoice < ActiveRecord::Base    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, "cannot be in the past")        end    end        def discount_cannot_be_greater_than_total_value        if discount > total_value            errors.add(:discount, "cannot be greater than total value")        end    endend

validate注册数据验证方法时还可以使用:on选项指定何时进行验证,可选值为:create or :update

处理验证错误

对实例errors集合的处理,最简单的方式是使用valid? or invalid?查看验证是否通过,完整的处理方法可以在ActiveModel::Errors中查询到。

errors & errors[]

实例的errors属性是一个哈希,键是每个属性的名字,值是错误消息字符串的数组。

errors.add

.add手动添加错误消息,例如.errors.add(:name, "cannot balabala") or .errors[:name] = "cannot balabala".full_message显示友好的错误消息,将属性名和错误消息进行组合。

errors[:base]

将错误消息添加到整个数据对象上,而不是具体的属性。他也是一个字符串数组。

errors.clear

清除错误消息集合,这并不会改变数据对象的合法性,因为下次调用valid? or invalid?时错误消息又会被添加到集合中。

errors.size

返回错误消息总数。

在视图中显示验证错误

略。

参考

https://ruby-china.github.io/rails-guides/v4.1/active_record_validations.html

0

评论区