Validations
- 类方法
- validate 自定义验证方法 (不需要属性,也不需要校验器;直接做校验)
ruby
class Invoice < ApplicationRecord
validate :active_customer, on: :create
def active_customer
errors.add(:customer_id, "is not active") unless customer.active?
end
end
- validates 和 validates! (需要属性,也需要校验器;间接做校验)
ruby
class Person < ApplicationRecord
validates :name, presence: true #校验器是PresenceValidator
end
#校验器PresenceValidator
class PresenceValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add attribute, "can't be blank" if value.blank?
end
end
- validates_each (需要属性,不需要校验器;直接做校验)
ruby
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
- validates_with 验证交给其他类做验证 (不需要属性,需要校验器;作用于所有对象,间接做校验)
ruby
class GoodnessValidator < ActiveModel::Validator
def validate(record)
# options = { :fields => [:first_name, :last_name] }
# options = { :abc => [:first_name, :last_name] }
if options[:fields].any?{|field| record.send(field) == "Evil" }
record.errors[:base] << "This person is evil"
end
end
end
class Person < ApplicationRecord
#fields是options哈希的一个key,也可以是别的例如 abc: [:first_name, :last_name]
validates_with GoodnessValidator, fields: [:first_name, :last_name]
end
经验:牢记 validate 发生在 save 之前,如果你喜欢用 before_save 之类的进行检验,记得加上 return false 或者改变习惯,用 validate :validate_method 类似写法进行校验。
Validator
- 自定义验证类(校验器)
- 自定义的验证类继承自 ActiveModel::Validator,必须实现 validate 方法,其参数是要验证的记录,然后验证这个记录是否有效。自定义的验证类通过 validates_with 方法调用
ruby
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 方法。这个方法接受三个参数:记录、属性名和属性值。它们分别对应模型实例、要验证的属性及其值。
ruby
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
# presence: true为内置的验证类,email: true为自定义的验证类,可以同时使用
# email: true 对应的验证类必须是EmailValidator, title:true 对应的验证类必须是TitlelValidator
validates :email, presence: true, email: true
end
- 自定义验证方法
自定义验证方法必须使用类方法 validate 注册,传入自定义验证方法名的符号形式 。
这个类方法可以接受多个符号,自定义的验证方法会按照注册的顺序执行 。
可以设置 :on 选项,指定什么时候验证。:on 的可选值为 :create 和 :update。
ruby
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
- valid? 校验是否通过,只有一个判断规则:当前 record 对象的 errors 值是否为空 errors.empty?
所以自己写校验方法或者校验器的时候,请记得设置 errors 的值。
Concern实现
ruby
# app/models/concerns/custom_validatable.rb
module CustomValidatable
extend ActiveSupport::Concern
included do
validate :custom_validate
end
def validate_custom_fields(fields)
CustomValidation.order(:position).each do |validation|
regex = Regexp.new(validation.regex)
fields.each do |field|
value = send(field)
unless value.match?(regex)
errors.add(field, validation.error_message)
end
end
end
end
end
ruby
# app/models/issue.rb
include CustomValidatable
def custom_validate
validate_custom_fields(['subject', 'description'])
end
validates_with实现
ruby
class WxMsgSecValidator < ActiveModel::Validator
# scene: 1 scene_profile 资料, 2 scene_comment 评论, 3 scene_post 论坛, 4 scene_log 社交日志
def validate(record)
if options[:fields].select { |field| record.send("#{field}_changed?") }.present?
case record.class.name
when 'YouKe'
@you_ke = record
else
@you_ke = record.you_ke
end
scene_hash = { desc: 2, content: 2 }
@you_ke = YouKe.where(is_robot: false).first if @you_ke&.is_robot?
open_id = @you_ke&.klly? ? @you_ke&.open_id : @you_ke&.wmh_open_id
return record.errors[:base] << '账号openid不存在' if open_id.nil?
options[:fields].select { |field| record.send("#{field}_changed?") }.each do |field|
if record.send(field).present?
is_deny = @you_ke.klly? ? !Weixin.msg_check(scene_hash[field], open_id, record.send(field)) : !WmhWeixin.msg_check(scene_hash[field], open_id, record.send(field))
record.errors.add(:base, '输入涉嫌违规') if is_deny
end
end
end
end
end
ruby
# app/models/issue.rb
include ActiveModel::Validations
validates_with WxMsgSecValidator, fields: %i[content]