Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Active Record 数据验证 #36

Open
huruji opened this issue May 4, 2018 · 0 comments
Open

Active Record 数据验证 #36

huruji opened this issue May 4, 2018 · 0 comments
Labels

Comments

@huruji
Copy link
Owner

huruji commented May 4, 2018

数据验证概览

为什么要做数据验证

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

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

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

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

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

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 返回错误消息总数。

@huruji huruji added the ruby label May 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant