专栏首页忽如寄的前端周刊Active Record 数据验证

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 条评论
登录 后参与评论

相关文章

  • Webpack系列——Webpack + xxx配合使用

    在webpack中使用Babel通过使用babel-loader即可,babel中的配置可以通过options选项进行配置。 安装:

    用户1515472
  • 使用Koa + mysql一小时搭建个人博客

    路由:koa-router 表单解析:koa-bodyparser 视图:koa-views、ejs session: koa-session-minim...

    用户1515472
  • Android布局笔记

    相对布局可以让控件之间互相确定关系,保证屏幕的局部范围内几个控件之间的关系不受外部影响。 相对布局位置的属性有

    用户1515472
  • Python股票数据分析

    python版本:3.4 最近在学习基于python的股票数据分析,其中主要用到了tushare和seaborn。tushare是一款财经类数据接口包,国内的...

    机器学习AI算法工程
  • 深度|Python股票数据分析

    最近在学习基于python的股票数据分析,其中主要用到了tushare和seaborn。 python版本:3.4 tushare是一款财经类数据接口包,国...

    灯塔大数据
  • 深入Python多进程通信原理与实战——图文

    继上节使用原生多进程并行运行,基于Redis作为消息队列完成了圆周率的计算,本节我们使用原生操作系统消息队列来替换Redis。

    老钱
  • 死磕YOLO系列,YOLOv2的自我修养

    YOLO 在当时是非常不错的算法,速度极快,但明显的缺陷就是精度问题特别是小尺寸目标检测问题上。

    Frank909
  • NVIDIA Deepstream 4.0笔记(三):智能交通场景应用

    本次笔记整理自NVIDIA 8月20日在线研讨会,原讲座标题:DEEPSTREAM SDK – ACCELERATING REAL-TIME AI BASED ...

    GPUS Lady
  • 换个字体解决Dreamweaver文字选不中的问题

      在使用Dreamweaver时,有时我们要选中一些字符进行编辑或删除,光标在英文字上面可以选中,在中文字上面就选不中,郁闷吧。比如在编辑下面这段文字的时候,...

    ytkah
  • Java第三方支付接入案例(支付宝)

    到蚂蚁金服注册开发者账号,注册地址:https://open.alipay.com,用你的 支付宝 账号扫码登录,完善个人信息,选择服务类型。

    朝雨忆轻尘

扫码关注云+社区

领取腾讯云代金券