Rails Validation

Published 4 months ago, last updated 4 months ago

之前为了给博客加上评论功能,想了很久怎样在不让读者注册的情况下保持一个独立账户。最后决定学习煎蛋的做法:填写邮箱和用户名,这样只要保证这俩一一对应就行。但是后端并没有为读者建立对应的Model,所以用户名和邮箱都只能作为评论的fields。为了保证用户名和邮箱都唯一,我们就需要自己写一个validation的模块了。正好Rails的文档非常翔实,就借机总结一下Rails validation的不同方式。

Validation什么时候发生

每当我们在rails中生成一个新的object时,这个object会附带一个方法new_record?,在Rails向数据库UPDATE和INSERT之后返回false,所以validation在每次create, save以及update之前都会运行。也有一部分方法会跳过validation,比如update_columnupdate_counters,文档里还是推荐大家尽可能避免使用这些方法。

Validation Helper

Rails提供了非常多的validator,包括非常常见的presence,length。使用validation helper的通常方式是

validate :field, :helper, option: {}

Rails的文档提供了非常多的例子。

Validation Options

不论是用我们自己写的Validator还是Rails自带的validator,我们都能够传入一些参数对validation做一些修改

  1. :allow_nil

    allow_nil 如果传入为true,Rails会跳过validation这一步。

  2. :allow_blank

    allow_blank类似于allow_nil, 对于空白的string之类就会跳过validation。

  3. :message

    message可以让我们自定义错误的信息,如果没有这个选项,Rails会自动填充默认的错误信息。message接收两种参数: string和 proc。

    1. String: 传入一个自定义的错误信息字符串,同时我们能够使用几个特殊的变量: %{value}, %{attribute}, %{model}.
    2. Proc: 接受两个参数 datamodel,其中model就是要验证的object,data包含了attribute和对应的value。
  4. :on

    我们可以通过 :on来决定在什么时候做validate。例如

    # it will be possible to update email with a duplicated value
    validates :email, uniqueness: true, on: :create
    

Raise Exception in validation

我们也可以在validation中加入strict: true,当validation失败时,Rails会抛出ActiveModel::StrictValidationFailed异常。

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

Conditional Validation

在有些情况下,我们只有在特定的情况下才会调用validator,我们可以传入一个if或是unless的选项。我们即可以传入一个函数的symbol,也可以传入一个新的Proc。

来自于官网的例子:

  1. 传入函数的Symbol
class Order < ApplicationRecord
  validates :card_number, presence: true, if: :paid_with_card?
 
  def paid_with_card?
    payment_type == "card"
  end
end
  1. 传入一个新的Proc
class Account < ApplicationRecord
  validates :password, confirmation: true,
    unless: Proc.new { |a| a.password.blank? }
end

Custom Validations

当我们需要验证的部分比较复杂的时候,Rails自带的validation可能就不符合我们的需求了,这个时候我们就可以写一个继承ActiveModel::Validator的Validator,这样Rails在保存一条新的记录前,会自动调用我们的Validator做验证。

回到之前对于评论的验证,当我们收到一条新的评论时,我们需要查看数据库,这个邮件地址是否有人已经使用,如果有,在验证用户名是否一样;如果还没有人使用,就检查这个用户名是否有人已经使用。所以Validation部分就比较简单了。

class DuplicateValidator < ActiveModel::Validator

  def validate(record)
    return if record.email.blank? || record.username.blank?
    @pre_record_with_same_email = Comment.find_by_email(record.email)
    if @pre_record_with_same_email.nil?
      @pre_record_with_same_username = Comment.find_by_username(record.username)
      unless @pre_record_with_same_username.nil?
        record.errors.add(:username, "has been taken by others.")
      end
    else
      unless @pre_record_with_same_email.username == record.username
        record.errors.add(:email, "is associated with another username.")
      end
    end
  end

end

我们只需要override validate这个方法,Rails在做validation的过程中便会调用这个函数。

另一个比较简单的方式就是用validate_each:

class DuplicateValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    # Customized validation..
  end
end

class Comment < ApplicationRecord
  validate :email, presence: true, duplicate: true
end

当然,另一种比较简单的方法就是直接写一个custom method,然后使用validate调用它。

def some_custom_validate_methods
end

class Person < ApplicationRecord
  validate :some_custom_validate_methods
end

Rails Validation Errors

所有的Rails errors都继承自 ActiveModel::Errors, 它提供了很多built in的方法让我们查看和修改validation errors。

errors本身是一个类似于hash的object,每一个attribute都会有一个array,包含了这个attribute的所有的validation errors。

  1. errors

    这个方法直接返回所有的错误。官网的例子比较直白

    class Person < ApplicationRecord
      validates :name, presence: true, length: { minimum: 3 }
    end
     
    person = Person.new
    person.valid? # => false
    person.errors.messages
     # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]}
     
    person = Person.new(name: "John Doe")
    person.valid? # => true
    person.errors.messages # => {}
    

  2. errors[:attribute]

    这个方法会返回一个与这个变量有关的所有错误。

  3. errors.add

    我们可以通过errors.add这个方法手动添加错误信息,例如之前我们在验证邮箱和用户名时:

    record.errors.add(:email, "is associated with another username.")
    

    当然,errors.messages[:name]返回的是一个array,我们也可以向这个array添加一个新的元素

    record.errors.messages[:email] << "is associated with another username"	
    
  4. errors.clear

    这个方法会清除当前error中所有的内容,但需要注意的是,清楚所有的errors并不代表这个object就能存到数据库中了,当我们调用save时,Rails会重新调用所有的validation,invalid的object会应该invalid。

  5. errors.size

    这个比较直白,返回错误的数量。

额。。没了


Comments

    No comment yet



Leave your comment