[Rails] Active Record Validations(Model 資料驗證)
keywords: 資料驗證, valid, error
資料驗證這件事在 Rails 的 MVC 三分天下的架構中,Controller 跟 Model 都可以做這件事,要在 View 裡寫 JavaScript 做檢查也可以,但這件事如果交給 Controller 或 View 來做的話,一來會讓程式碼的邏輯變得更複雜,二來這個驗證也不容易被重複使用,也不容易被測試,所以資料機制寫在 Model 裡是比較合理而且單純的。
Active Record Validations @ RailsGuides
Model 資料驗證設定
除了 presence 之外,Rails 還有提供其它像是 uniqueness、length 或 numericality 等便利的驗證器,使用方法可直接參考 Rails Guide 的 Active Record Validations 章節。
範例一
#####################################################
##             在 Model 中進行資料驗證                ##
#####################################################
class Candidate < ApplicationRecord
  validates :name, presence: true                # 寫法一
  validates_presence_of :party                   # 寫法二
end
範例二
validates:是標準 Rails 的驗證器(validator)
presence: true:則是告訴驗證器要驗證每個欄位是否存在,而且不是空值。
numericality: 用來確認內容是有效的數值
uniqueness:檢驗資料庫中有無具有相同 title 的列
allow_blank:用來允許欄位是空值
class Product < ApplicationRecord
  validates :title, :description, :image_url, presence: true
  validates :price, numericality: {greater_than_or_equal_to: 0.01}
  validates :title, uniqueness: true
  validates :image_url, allow_blank: true, format:{
      with: %r{\.(gif|jpg|png)\Z}i,
      message: 'must be a URL for GIF, JPG or PNG image.'
  }
end
檢視錯誤訊息
# 在 Rails Console 中測試
c1 = Candidate.new
c1.errors.any?                 # 回傳 false,檢驗 c1 有無錯誤
c1.valid?                      # 驗證資料格式是否正確(透過 save 也會進行驗證)
c1.errors.any?                 # 回傳 true
c1.errors.full_messages        # 回傳 "Name can't be blank",顯示錯誤訊息內容
c1.save(validates: false)      # 跳過資料驗證
注意事項
雖然驗證功能很方便,但並不是每種方法都會觸發驗證,僅有以下這些方法會觸發驗證 create, create!, save, save!, update, update!,而 toggle! 或 increment! 等方法會跳過驗證流程。
有驚嘆號版本的,如果驗證未通過會產生錯誤訊息,而沒有驚嘆號版本則僅會回傳該 Model 的一個空物件。
客制化資料驗證
方法一:遵循 Rails 驗證器規則
這個驗證器可以跟其它內建的驗證器一起混著使用,使用起來會更簡潔。要寫這樣的驗證器需要符合 Rails Validator 的命名規則:
- 參數是 begin_with_ruby 的話,類別名稱則是 BeginWithRuby 加上 Validator,並繼承自 ActiveModel::EachValidator 類別。
 - 必須實作 validate_each 方法。
 
# 客制化驗證器(class)要放在驗證步驟(validates)前
class BeginWithRubyValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value.starts_with? 'Ruby'
      record.errors[attribute] << "必需是 Ruby 開頭喔!"
    end
  end
end
validates :name, begin_with_ruby: true
方法二:使用 validate 自己寫判斷
class Candidate < ApplicationRecord
  validate :name_validator         # 這裡是 validate(單數)不是上面的 validates
  private
  def name_validator
    unless name.starts_with? 'Ruby'
      errors[:name] << '必需是 Ruby 開頭喔!'       # 當不符合規定時,就在 errors 的 Hash 裡面塞錯誤訊息。
    end
  end
end