v7.0.0
更多資訊請前往 rubyonrails.org: 更多在 Ruby on Rails

Active Record 驗證

本指南教您如何在物件進入之前驗證物件的狀態 資料庫使用 Active Record 的驗證功能。

閱讀本指南後,您將瞭解:

1 view 上的驗證

這是一個非常簡單的驗證示例:

class Person < ApplicationRecord
  validates :name, presence: true
end
irb> Person.create(name: "John Doe").valid?
=> true
irb> Person.create(name: nil).valid?
=> false

如您所見,我們的驗證讓我們知道我們的 Person 無效 沒有 name 屬性。第二個 Person 不會持久化到 資料庫。

在我們深入研究更多細節之前,讓我們先談談驗證如何適應 您的應用程式的大圖。

1.1 為什麼要使用驗證?

驗證用於確保僅將有效資料儲存到您的 資料庫。例如,確保您的應用程式 每個使用者都提供一個有效的電子郵件地址和郵寄地址。 Model 級 驗證是確保僅將有效資料儲存到您的 資料庫。它們與資料庫無關,終端使用者無法繞過,並且 方便測試和維護。 Rails 提供了內建的 helpers 用於常見的 需要,並允許您建立自己的驗證方法。

還有其他幾種方法可以在將資料儲存到您的 資料庫,包括本機資料庫約束、客戶端驗證和 controller 級別的驗證。下面總結一下優缺點:

  • 資料庫約束和/或儲存過程使驗證機制 依賴於資料庫,並且會使測試和維護更加困難。 但是,如果您的資料庫被其他應用程式使用,這可能是一個不錯的選擇 在資料庫級別使用一些約束的想法。此外, 資料庫級驗證可以安全地處理某些事情(例如唯一性 在頻繁使用的表中),否則很難實現。
  • 客戶端驗證可能很有用,但如果使用則通常不可靠 獨自的。如果它們是使用 JavaScript 實現的,它們可能會被繞過,如果 使用者瀏覽器中的 JavaScript 已關閉。但是,如果結合 其他技術,客戶端驗證可以是一種方便的方式來提供 使用者在使用您的網站時獲得即時反饋。
  • Controller 級別的驗證可能很容易使用,但通常會變成 笨重且難以測試和維護。只要有可能,這是一個很好的 保持您的 controllers 瘦的想法,因為它會使您的應用程式 從長遠來看,很高興與之合作。

在某些特定情況下選擇這些。這是 Rails 團隊的意見 model 級別的驗證在大多數情況下是最合適的。

1.2 何時進行驗證?

Active Record 物件有兩種:對應一行的物件 在您的資料庫中,而那些不在您的資料庫中。當您建立一個新物件時,對於 使用 new 方法的示例,該物件不屬於資料庫 然而。一旦您對該物件呼叫 save,它將被儲存到 適當的資料庫表。 Active Record 使用 new_record? 實例 方法來確定一個物件是否已經在資料庫中。 考慮以下 Active Record 類:

class Person < ApplicationRecord
end

我們可以透過檢視一些 bin/rails console 輸出來瞭解它是如何工作的:

irb> p = Person.new(name: "John Doe")
=> #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>

irb> p.new_record?
=> true

irb> p.save
=> true

irb> p.new_record?
=> false

建立和儲存新記錄將傳送 SQL INSERT 操作到 資料庫。更新現有記錄將傳送 SQL UPDATE 操作 反而。驗證通常在這些命令傳送到 資料庫。如果任何驗證失敗,該物件將被標記為無效並 Active Record 不會執行 INSERTUPDATE 操作。這避免了 在資料庫中儲存無效物件。您可以選擇具有特定的 在建立、儲存或更新物件時執行驗證。

有多種方法可以更改資料庫中物件的狀態。 有些方法會觸發驗證,但有些方法不會。這意味著它是 如果您不是,則可以將資料庫中的物件以無效狀態儲存 小心。

以下方法觸發驗證,並將物件儲存到 僅當物件有效時才使用資料庫:

  • create
  • create!
  • save
  • save!
  • update
  • update!

如果記錄無效,bang 版本(例如 save!)會引發異常。 非 bang 版本不會:saveupdate 返回 false,並且 create 返回物件。

1.3 跳過驗證

以下方法跳過驗證,並將物件儲存到 資料庫而不管其有效性。應謹慎使用它們。

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • insert
  • insert!
  • insert_all
  • insert_all!
  • toggle!
  • touch
  • touch_all
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters
  • upsert
  • upsert_all

請注意,如果透過validate,則save還可以跳過驗證: false 作為引數。應謹慎使用此技術。

  • save(validate: false)

1.4 valid?invalid?

在儲存 Active Record 物件之前,Rails 會執行您的驗證。 如果這些驗證產生任何錯誤,Rails 不會儲存該物件。

您也可以自行執行這些驗證。 valid? 觸發您的驗證 如果在物件中沒有發現錯誤,則返回真,否則返回假。 正如你在上面看到的:

class Person < ApplicationRecord
  validates :name, presence: true
end
irb> Person.create(name: "John Doe").valid?
=> true
irb> Person.create(name: nil).valid?
=> false

Active Record 執行驗證後,可以訪問發現的任何錯誤 透過 errors 實例方法,該方法返回錯誤集合。 根據定義,如果此集合在執行後為空,則物件是有效的 驗證。

注意用new實例化的物件不會報錯 即使它在技術上無效,因為驗證是自動執行的 僅當物件被儲存時,例如使用 createsave 方法。

class Person < ApplicationRecord
  validates :name, presence: true
end
irb> p = Person.new
=> #<Person id: nil, name: nil>
irb> p.errors.size
=> 0

irb> p.valid?
=> false
irb> p.errors.objects.first.full_message
=> "Name can't be blank"

irb> p = Person.create
=> #<Person id: nil, name: nil>
irb> p.errors.objects.first.full_message
=> "Name can't be blank"

irb> p.save
=> false

irb> p.save!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

irb> Person.create!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

invalid?valid? 的倒數。它會觸發您的驗證, 如果在物件中發現任何錯誤,則返回 true,否則返回 false。

1.5 errors[]

要驗證物件的特定屬性是否有效,您可以 使用 [errors[:attribute]][Errors#squarebrackets]。它返回一個包含所有錯誤訊息的陣列 :attribute。如果指定的屬性沒有錯誤,則為空陣列 被退回。

此方法僅在 after 驗證已執行時有用,因為它僅 檢查錯誤集合並且不會觸發驗證本身。它是 與上面解釋的 ActiveRecord::Base#invalid? 方法不同,因為 它不驗證整個物件的有效性。它只會檢查以檢視 是否在物件的單個屬性上發現錯誤。

class Person < ApplicationRecord
  validates :name, presence: true
end
irb> Person.new.errors[:name].any?
=> false
irb> Person.create.errors[:name].any?
=> true

我們將在 使用驗證 錯誤 部分。

2 驗證 Helpers

Active Record 提供了許多您可以使用的預定義驗證 helpers 直接在您的類定義中。這些 helpers 提供通用驗證 規則。每次驗證失敗時,都會向物件的 errors 集合,這與屬性相關聯 驗證。

每個 helper 接受任意數量的屬性名稱,因此使用單個 在一行程式碼中,您可以向多個屬性新增相同型別的驗證。

它們都接受 :on:message 選項,它們定義何時 應該執行驗證並且應該將什麼訊息新增到 errors 如果失敗,則分別收集。 :on 選項採用 values 之一 :create:update。存在預設錯誤 訊息為每一個驗證helpers。這些訊息用於 未指定 :message 選項。讓我們來看看每一個 可用 helpers。

2.1 acceptance

此方法驗證使用者介面上的複選框在 表格已提交。這通常用於使用者需要同意您的 應用程式的服務條款,確認已閱讀某些文字,或任何類似的 概念。

class Person < ApplicationRecord
  validates :terms_of_service, acceptance: true
end

僅當 terms_of_service 不是 nil 時才執行此檢查。 此 helper 的預設錯誤訊息是“必須接受”。 您還可以透過 message 選項傳入自定義訊息。

class Person < ApplicationRecord
  validates :terms_of_service, acceptance: { message: 'must be abided' }
end

它還可以接收一個 :accept 選項,它決定了允許的 values 這將被視為接受。它預設為 ['1', true] 並且可以 容易改變。

class Person < ApplicationRecord
  validates :terms_of_service, acceptance: { accept: 'yes' }
  validates :eula, acceptance: { accept: ['TRUE', 'accepted'] }
end

此驗證非常特定於 Web 應用程式,這 “接受”不需要記錄在您的資料庫中的任何位置。如果你 沒有它的欄位,helper 將建立一個虛擬屬性。如果 該欄位確實存在於您的資料庫中,必須將 accept 選項設定為 或包含 true 否則驗證將不會執行。

2.2 validates_associated

當您的模型具有 associations 和其他 models 時,您應該使用此 helper 它們也需要得到驗證。當您嘗試儲存物件時,valid? 將呼叫每個關聯的物件。

class Library < ApplicationRecord
  has_many :books
  validates_associated :books
end

此驗證適用於所有 association 型別。

請勿在 associations 的兩端使用 validates_associated。 他們會在無限迴圈中互相呼叫。

validates_associated 的預設錯誤訊息是 "is invalid"。筆記 每個關聯的物件都將包含自己的 errors 集合;錯誤做 不會冒泡到呼叫 model。

2.3 confirmation

當您有兩個應接收的文字欄位時,您應該使用此 helper 完全一樣的內容。例如,您可能想要確認電子郵件地址 或密碼。此驗證建立一個虛擬屬性,其名稱是 必須附加“_confirmation”確認的欄位的名稱。

class Person < ApplicationRecord
  validates :email, confirmation: true
end

在您的 view 模板中,您可以使用類似

<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>

僅當 email_confirmation 不是 nil 時才執行此檢查。要求 確認,確保為確認屬性新增存在檢查 (我們稍後將在本指南中介紹 presence):

class Person < ApplicationRecord
  validates :email, confirmation: true
  validates :email_confirmation, presence: true
end

還有一個 :case_sensitive 選項可以用來定義 確認約束將區分大小寫。該選項預設為 真的。

class Person < ApplicationRecord
  validates :email, confirmation: { case_sensitive: false }
end

此 helper 的預設錯誤訊息是 "不匹配確認"

2.4 comparison

此檢查將驗證任何兩個可比較的 values 之間的比較。 驗證器需要提供一個比較選項。每個選項都接受一個 value、proc 或 symbol。任何包含 Comparable 的類都可以進行比較。

class Promotion < ApplicationRecord
  validates :start_date, comparison: { greater_than: :end_date }
end

這些選項都受支援:

  • :greater_than - 指定 value 必須大於提供的 value。此選項的預設錯誤訊息是 " 必須大於 %{數數}”
  • :greater_than_or_equal_to - 指定 value 必須大於或 等於提供的 value。此選項的預設錯誤訊息是 "必須大於或等於 %{count}"
  • :equal_to - 指定 value 必須等於提供的 value。這 此選項的預設錯誤訊息是 "必須等於 %{count}"
  • :less_than - 指定 value 必須小於提供的 value。這 此選項的預設錯誤訊息是 "必須小於 %{count}"
  • :less_than_or_equal_to - 指定 value 必須小於或等於 提供的 value。此選項的預設錯誤訊息是 "must be 小於或等於 %{count}"
  • :other_than - 指定 value 必須不是提供的 value。 此選項的預設錯誤訊息是 "必須不是 %{count}"

2.5 exclusion

此 helper 驗證屬性的 values 未包含在給定的 放。事實上,這個集合可以是任何可列舉的物件。

class Account < ApplicationRecord
  validates :subdomain, exclusion: { in: %w(www us ca jp),
    message: "%{value} is reserved." }
end

exclusion helper 有一個選項 :in 接收一組 values 將不接受已驗證的屬性。 :in 選項有一個 如果您願意,可以將別名 :within 用於相同目的。 此示例使用 :message 選項來展示如何包含 屬性的 value。有關訊息引數的完整選項,請參閱 訊息文件

預設錯誤訊息是 "is reserved"

2.6 format

這個 helper 透過測試它們是否匹配一個屬性來驗證屬性的 values 給定正則表示式,使用 :with 選項指定。

class Product < ApplicationRecord
  validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
    message: "only allows letters" }
end

或者,您可以使用 :without 選項要求指定的屬性匹配正則表示式。

預設錯誤訊息是“無效”

2.7 inclusion

此 helper 驗證屬性的 values 是否包含在給定集合中。 事實上,這個集合可以是任何可列舉的物件。

class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
    message: "%{value} is not a valid size" }
end

inclusion helper 有一個選項 :in 接收一組 values 將被接受。 :in 選項有一個名為 :within 的別名,您可以 如果您願意,也可以用於相同的目的。前面的例子使用 :message 選項顯示如何包含屬性的 value。對於完整 選項請參閱訊息文件

此 helper 的預設錯誤訊息是 "未包含在列表中"

2.8 length

此 helper 驗證屬性的 values 的長度。它提供了一個 多種選項,因此您可以以不同方式指定長度約束:

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) - 屬性長度必須包含在給定的 間隔。此選項的 value 必須是一個範圍。
  • :is - 屬性長度必須等於給定的 value。

預設錯誤訊息取決於長度驗證的型別 執行。您可以使用 :wrong_length 自定義這些訊息, :too_long:too_short 選項以及 %{count} 作為佔位符 與正在使用的長度約束相對應的數字。您仍然可以使用 :message 選項指定錯誤訊息。

class Person < ApplicationRecord
  validates :bio, length: { maximum: 1000,
    too_long: "%{count} characters is the maximum allowed" }
end

請注意,預設錯誤訊息是複數形式(例如,“太短(最少 是 %{count} 個字元)”)。因此,當 :minimum 為 1 時,您應該 提供自定義訊息或使用 presence: true 代替。什麼時候 :in:within 的下限為 1,您應該提供一個 自定義訊息或在 length 之前呼叫 presence

2.9 numericality

此 helper 驗證您的屬性只有數字 values。經過 預設情況下,它將匹配一個可選符號,後跟一個整數或浮點數 點數。

要指定只允許整數, 將 :only_integer 設定為 true。然後它將使用

/\A[+-]?\d+\z/

正則表示式來驗證屬性的 value。否則,它會嘗試 使用 Float 將 value 轉換為數字。 Float 使用列的精度 value 或 15 轉換為 BigDecimal

class Player < ApplicationRecord
  validates :points, numericality: true
  validates :games_played, numericality: { only_integer: true }
end

:only_integer 的預設錯誤訊息是 "must be an integer"

除了:only_integer,這個helper還接受以下選項新增 對可接受的 values 的約束:

  • :greater_than - 指定 value 必須大於提供的 value。此選項的預設錯誤訊息是 " 必須大於 %{數數}”
  • :greater_than_or_equal_to - 指定 value 必須大於或 等於提供的 value。此選項的預設錯誤訊息是 "必須大於或等於 %{count}"
  • :equal_to - 指定 value 必須等於提供的 value。這 此選項的預設錯誤訊息是 "必須等於 %{count}"
  • :less_than - 指定 value 必須小於提供的 value。這 此選項的預設錯誤訊息是 "必須小於 %{count}"
  • :less_than_or_equal_to - 指定 value 必須小於或等於 提供的 value。此選項的預設錯誤訊息是 "must be 小於或等於 %{count}"
  • :other_than - 指定 value 必須不是提供的 value。 此選項的預設錯誤訊息是 "必須不是 %{count}"
  • :in - 指定 value 必須在提供的範圍內。 此選項的預設錯誤訊息是 "must be in %{count}"
  • :odd - 如果設定為 true,則指定 value 必須是奇數。這 此選項的預設錯誤訊息是 “必須是奇數”
  • :even - 如果設定為 true,則指定 value 必須是偶數。這 此選項的預設錯誤訊息是 “必須是偶數”

預設情況下,numericality 不允許 nil values。您可以使用 allow_nil: true 選項來允許它。

未指定任何選項時的預設錯誤訊息是 "不是數字"

2.10 presence

此 helper 驗證指定的屬性不為空。它使用 blank? 方法來檢查 value 是 nil 還是空白字串,即 是一個空字串或由空格組成的字串。

class Person < ApplicationRecord
  validates :name, :login, :email, presence: true
end

如果你想確定一個 association 存在,你需要測試 是否存在關聯物件本身,而不是使用的外部 key 對映 association。這樣不僅查了國外的key 不為空,而且引用的物件存在。

class Supplier < ApplicationRecord
  has_one :account
  validates :account, presence: true
end

為了驗證需要存在的關聯記錄,您必須 為 association 指定 :inverse_of 選項:

如果要確保 association 存在且有效,則還需要使用 validates_associated

class Order < ApplicationRecord
  has_many :line_items, inverse_of: :order
end

如果您驗證透過 has_one 關聯的物件的存在或 has_many 關係,它會檢查物件既不是 blank? 也不是 marked_for_destruction?

由於 false.blank? 為真,如果你想驗證一個布林值的存在 您應該使用以下驗證之一:

validates :boolean_field_name, inclusion: [true, false]
validates :boolean_field_name, exclusion: [nil]

透過使用這些驗證之一,您將確保 value 不會是 nil 在大多數情況下,這將導致 NULL value。

2.11 absence

此 helper 驗證指定的屬性不存在。它使用 present? 方法來檢查 value 是否不是 nil 或空白字串,即 是一個空字串或由空格組成的字串。

class Person < ApplicationRecord
  validates :name, :login, :email, absence: true
end

如果你想確定一個 association 不存在,你需要測試 關聯物件本身是否不存在,而不是使用的外部 key 對映 association。

class LineItem < ApplicationRecord
  belongs_to :order
  validates :order, absence: true
end

為了驗證需要缺席的關聯記錄,您必須 為 association 指定 :inverse_of 選項:

class Order < ApplicationRecord
  has_many :line_items, inverse_of: :order
end

如果您驗證不存在透過 has_one 關聯的物件或 has_many 關係,它會檢查物件既不是 present? 也不是 marked_for_destruction?

由於 false.present? 是假的,如果你想驗證一個布林值的缺失 您應該使用 validates :field_name, exclusion: { in: [true, false] } 欄位。

預設錯誤訊息是“必須為空”

2.12 uniqueness

此 helper 驗證屬性的 value 在 物件被儲存。它不會在資料庫中建立唯一性約束, 所以可能會發生兩個不同的資料庫連線建立兩個記錄 對於您打算唯一的列,使用相同的 value。為了避免這種情況, 您必須在資料庫中的該列上建立唯一索引。

class Account < ApplicationRecord
  validates :email, uniqueness: true
end

驗證透過對 model 的表執行 SQL 查詢來進行, 在該屬性中搜索具有相同 value 的現有記錄。

有一個 :scope 選項可用於指定一個或多個屬性 用於限制唯一性檢查:

class Holiday < ApplicationRecord
  validates :name, uniqueness: { scope: :year,
    message: "should happen once per year" }
end

如果您希望使用 :scope 選項建立資料庫約束以防止可能違反唯一性驗證,則必須在資料庫的兩列上建立唯一索引。有關多列索引的更多詳細資訊,請參閱 MySQL 手冊PostgreSQL 手冊 獲取引用一組列的唯一約束的示例。

還有一個 :case_sensitive 選項可以用來定義 唯一性約束將區分大小寫。該選項預設為 真的。

class Person < ApplicationRecord
  validates :name, uniqueness: { case_sensitive: false }
end

請注意,某些資料庫配置為執行不區分大小寫 無論如何搜尋。

預設的錯誤資訊是“已經被佔用”

2.13 validates_with

此 helper 將記錄傳遞給單獨的類進行驗證。

class GoodnessValidator < ActiveModel::Validator
  def validate(record)
    if record.first_name == "Evil"
      record.errors.add :base, "This person is evil"
    end
  end
end

class Person < ApplicationRecord
  validates_with GoodnessValidator
end

新增到 record.errors[:base] 的錯誤與記錄的狀態有關 作為一個整體,而不是特定的屬性。

validates_with helper 需要一個類,或者一個類列表來用於 驗證。 validates_with 沒有預設錯誤訊息。你必須 手動將錯誤新增到驗證器類中記錄的錯誤集合中。

要實現驗證方法,您必須定義一個 record 引數, 這是要驗證的記錄。

與所有其他驗證一樣,validates_with 採用 :if:unless:on 選項。如果您傳遞任何其他選項,它會將這些選項傳送到 驗證器類為 options

class GoodnessValidator < ActiveModel::Validator
  def validate(record)
    if options[:fields].any? { |field| record.send(field) == "Evil" }
      record.errors.add :base, "This person is evil"
    end
  end
end

class Person < ApplicationRecord
  validates_with GoodnessValidator, fields: [:first_name, :last_name]
end

請注意,驗證器將在整個應用程式中僅初始化一次 生命週期,而不是在每次驗證執行時,所以要小心使用實例 裡面的變數。

如果您的驗證器足夠複雜以至於您需要實例變數,您可以 輕鬆地使用普通的舊 Ruby 物件代替:

class Person < ApplicationRecord
  validate do |person|
    GoodnessValidator.new(person).validate
  end
end

class GoodnessValidator
  def initialize(person)
    @person = person
  end

  def validate
    if some_complex_condition_involving_ivars_and_private_methods?
      @person.errors.add :base, "This person is evil"
    end
  end

  # ...
end

2.14 validates_each

此 helper 驗證針對塊的屬性。它沒有預定義 驗證功能。您應該使用塊建立一個,並且每個屬性 傳遞給 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

該塊接收記錄、屬性名稱和屬性的 value。 您可以做任何事情來檢查塊中的有效資料。如果你的 驗證失敗,您應該向 model 新增錯誤,因此 使其無效。

3 常用驗證選項

這些是常見的驗證選項:

3.1 :allow_nil

當正在驗證的 value 是時,:allow_nil 選項跳過驗證 nil

class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
    message: "%{value} is not a valid size" }, allow_nil: true
end

有關訊息引數的完整選項,請參閱 訊息文件

3.2 :allow_blank

:allow_blank 選項類似於 :allow_nil 選項。這個選項 如果屬性的 value 是 blank?,例如 nil 或 例如空字串。

class Topic < ApplicationRecord
  validates :title, length: { is: 5 }, allow_blank: true
end
irb> Topic.create(title: "").valid?
=> true
irb> Topic.create(title: nil).valid?
=> true

3.3 :message

正如您已經看到的,:message 選項允許您指定訊息 驗證失敗時將新增到 errors 集合中。當這 未使用選項,Active Record 將使用各自的預設錯誤訊息 對於每個驗證 helper。 :message 選項接受 StringProc

A String :message value 可以選擇包含任何/全部 %{value}%{attribute}%{model} 將在以下情況下動態替換 驗證失敗。此替換是使用 I18n gem 完成的,並且 佔位符必須完全匹配,不允許有空格。

A Proc :message value 有兩個引數:被驗證的物件,以及 具有 :model:attribute:value key-value 對的雜湊。

class Person < ApplicationRecord
  # Hard-coded message
  validates :name, presence: { message: "must be given please" }

  # Message with dynamic attribute value. %{value} will be replaced
  # with the actual value of the attribute. %{attribute} and %{model}
  # are also available.
  validates :age, numericality: { message: "%{value} seems wrong" }

  # Proc
  validates :username,
    uniqueness: {
      # object = person object being validated
      # data = { model: "Person", attribute: "Username", value: <username> }
      message: ->(object, data) do
        "Hey #{object.name}, #{data[:value]} is already taken."
      end
    }
end

3.4 :on

:on 選項可讓您指定應何時進行驗證。這 所有內建驗證 helpers 的預設行為是在儲存時執行 (無論是在建立新記錄時還是在更新記錄時)。如果你 想要改變它,你可以使用 on: :create 來執行驗證,只有當 新記錄被建立或 on: :update 僅在記錄時執行驗證 已更新。

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

  # it will be possible to create the record with a non-numerical age
  validates :age, numericality: true, on: :update

  # the default (validates on both create and update)
  validates :name, presence: true
end

您還可以使用 on: 來定義自定義上下文。自定義上下文需要 透過將上下文的名稱傳遞給 valid? 來顯式觸發, invalid?save

class Person < ApplicationRecord
  validates :email, uniqueness: true, on: :account_setup
  validates :age, numericality: true, on: :account_setup
end
irb> person = Person.new(age: 'thirty-three')
irb> person.valid?
=> true
irb> person.valid?(:account_setup)
=> false
irb> person.errors.messages
=> {:email=>["has already been taken"], :age=>["is not a number"]}

person.valid?(:account_setup) 執行兩個驗證而不儲存 model。 person.save(context: :account_setup) 驗證 person 在 儲存前的 account_setup 上下文。

當由顯式上下文觸發時,會針對該上下文執行驗證, 以及任何沒有上下文的驗證。

class Person < ApplicationRecord
  validates :email, uniqueness: true, on: :account_setup
  validates :age, numericality: true, on: :account_setup
  validates :name, presence: true
end
irb> person = Person.new
irb> person.valid?(:account_setup)
=> false
irb> person.errors.messages
=> {:email=>["has already been taken"], :age=>["is not a number"], :name=>["can't be blank"]}

4 嚴格驗證

您還可以將驗證指定為嚴格並提高 ActiveModel::StrictValidationFailed 當物件無效時。

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

還可以將自定義異常傳遞給 :strict 選項。

class Person < ApplicationRecord
  validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
irb> Person.new.valid?
TokenGenerationException: Token can't be blank

5 條件驗證

有時只有在給定謂詞時才驗證物件才有意義 很滿意。您可以透過使用 :if:unless 選項來做到這一點,它們 可以採用 symbol、ProcArray。您可以使用 :if 當您想指定驗證應該發生的時間時的選項。如果你 想要指定驗證不應該發生的時間,那麼您可以使用 :unless 選項。

5.1 使用 Symbol 和 :if:unless

您可以將 :if:unless 選項與對應的 symbol 相關聯 到將在驗證發生之前被呼叫的方法的名稱。 這是最常用的選項。

class Order < ApplicationRecord
  validates :card_number, presence: true, if: :paid_with_card?

  def paid_with_card?
    payment_type == "card"
  end
end

5.2 使用帶有 :if:unless 的 Proc

可以將 :if:unlessProc 物件相關聯 這將被呼叫。使用 Proc 物件使您能夠編寫一個 內聯條件而不是單獨的方法。此選項最適合 單襯。

class Account < ApplicationRecord
  validates :password, confirmation: true,
    unless: Proc.new { |a| a.password.blank? }
end

由於 LambdasProc 的一種,所以它們也可以用來寫內聯 條件更短。

validates :password, confirmation: true, unless: -> { password.blank? }

5.3 分組條件驗證

有時讓多個驗證使用一個條件很有用。它可以 使用 with_options 可以輕鬆實現。

class User < ApplicationRecord
  with_options if: :is_admin? do |admin|
    admin.validates :password, length: { minimum: 10 }
    admin.validates :email, presence: true
  end
end

with_options 塊內的所有驗證將自動具有 透過條件 if: :is_admin?

5.4 結合驗證條件

另一方面,當多個條件定義是否驗證 應該發生,可以使用 Array。此外,您可以同時應用 :if:unless 到相同的驗證。

class Computer < ApplicationRecord
  validates :mouse, presence: true,
                    if: [Proc.new { |c| c.market.retail? }, :desktop?],
                    unless: Proc.new { |c| c.trackpad.present? }
end

驗證僅在所有 :if 條件且不滿足任何條件時執行 :unless 條件評估為 true

6 執行自定義驗證

當內建驗證 helpers 不足以滿足您的需求時,您可以 根據需要編寫自己的驗證器或驗證方法。

6.1 自定義驗證器

自定義驗證器是從 ActiveModel::Validator 繼承的類。這些 類必須實現 validate 方法,該方法將記錄作為引數 並對其進行驗證。自定義驗證器使用 validates_with 方法。

class MyValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.start_with? 'X'
      record.errors.add :name, "Need a name starting with X please!"
    end
  end
end

class Person
  include ActiveModel::Validations
  validates_with MyValidator
end

新增自定義驗證器以驗證單個屬性的最簡單方法 是用方便的ActiveModel::EachValidator。在這種情況下,自定義 驗證器類必須實現一個 validate_each 方法,該方法需要三個 引數:記錄、屬性和 value。這些對應於實例, 要驗證的屬性,以及傳入的屬性的value 實例。

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors.add attribute, (options[:message] || "is not an email")
    end
  end
end

class Person < ApplicationRecord
  validates :email, presence: true, email: true
end

如示例所示,您還可以將標準驗證與您的 自己的自定義驗證器。

6.2 自定義方法

您還可以建立驗證 models 狀態的方法並新增 errors 集合無效時的錯誤。那麼你必須 使用 validate 註冊這些方法 類方法,傳入 symbols 作為驗證方法的名稱。

您可以為每個類方法和各自的方法傳遞多個 symbol 驗證將按照與註冊相同的順序執行。

valid? 方法將驗證錯誤集合是否為空, 所以你的自定義驗證方法應該在你 希望驗證失敗:

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? 時都會執行此類驗證 或儲存物件。但是也可以控制何時執行這些 透過為 validate 方法提供 :on 選項來自定義驗證, 使用::create:update

class Invoice < ApplicationRecord
  validate :active_customer, on: :create

  def active_customer
    errors.add(:customer_id, "is not active") unless customer.active?
  end
end

7 處理驗證錯誤

valid?invalid? 方法僅提供有效性的摘要狀態。但是,您可以使用 errors 集合中的各種方法深入挖掘每個錯誤。

下面列出了最常用的方法。有關所有可用方法的列表,請參閱 ActiveModel::Errors 文件。

7.1 errors

您可以透過該閘道器深入瞭解每個錯誤的各種詳細資訊。

這將返回一個包含所有錯誤的類 ActiveModel::Errors 的實例, 每個錯誤都由一個 ActiveModel::Error 物件表示。

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

irb> person = Person.new(name: "John Doe")
irb> person.valid?
=> true
irb> person.errors.full_messages
=> []

7.2 errors[]

[errors[]][Errors#squarebrackets] 用於檢查特定屬性的錯誤訊息。它返回一個字串陣列,其中包含給定屬性的所有錯誤訊息,每個字串包含一個錯誤訊息。如果沒有與屬性相關的錯誤,則返回一個空陣列。

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new(name: "John Doe")
irb> person.valid?
=> true
irb> person.errors[:name]
=> []

irb> person = Person.new(name: "JD")
irb> person.valid?
=> false
irb> person.errors[:name]
=> ["is too short (minimum is 3 characters)"]

irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors[:name]
=> ["can't be blank", "is too short (minimum is 3 characters)"]

7.3 errors.where 和錯誤物件

有時,除了訊息之外,我們可能還需要有關每個錯誤的更多資訊。每個錯誤都封裝成一個ActiveModel::Error物件,where方法是最常見的訪問方式。

where 返回一個錯誤物件陣列,按不同程度的條件過濾。

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false

irb> person.errors.where(:name)
=> [ ... ] # all errors for :name attribute

irb> person.errors.where(:name, :too_short)
=> [ ... ] # :too_short errors for :name attribute

您可以從這些錯誤物件中讀取各種資訊:

irb> error = person.errors.where(:name).last

irb> error.attribute
=> :name
irb> error.type
=> :too_short
irb> error.options[:count]
=> 3

您還可以生成錯誤訊息:

irb> error.message
=> "is too short (minimum is 3 characters)"
irb> error.full_message
=> "Name is too short (minimum is 3 characters)"

full_message 方法生成更加使用者友好的訊息,並在前面加上大寫的屬性名稱。

7.4 errors.add

add 方法透過獲取 attribute、錯誤 type 和其他選項雜湊來建立錯誤物件。這對於編寫自己的驗證器很有用。

class Person < ApplicationRecord
  validate do |person|
    errors.add :name, :too_plain, message: "is not cool enough"
  end
end
irb> person = Person.create
irb> person.errors.where(:name).first.type
=> :too_plain
irb> person.errors.where(:name).first.full_message
=> "Name is not cool enough"

7.5 errors[:base]

您可以新增與物件整體狀態相關的錯誤,而不是與特定屬性相關的錯誤。無論其屬性的values如何,當您想說該物件無效時,都可以在:base中新增錯誤。

class Person < ApplicationRecord
  validate do |person|
    errors.add :base, :invalid, message: "This person is invalid because ..."
  end
end
irb> person = Person.create
irb> person.errors.where(:base).first.full_message
=> "This person is invalid because ..."

7.6 errors.clear

當您有意清除 errors 集合時,將使用 clear 方法。當然,在無效物件上呼叫 errors.clear 實際上不會使其有效:errors 集合現在將為空,但是下次您呼叫 valid? 或任何嘗試將此物件儲存到資料庫的方法時,驗證將再次執行.如果任何驗證失敗,則將再次填充 errors 集合。

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.empty?
=> false

irb> person.errors.clear
irb> person.errors.empty?
=> true

irb> person.save
=> false

irb> person.errors.empty?
=> false

7.7 errors.size

size 方法返回物件的錯誤總數。

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.size
=> 2

irb> person = Person.new(name: "Andrea", email: "andrea@example.com")
irb> person.valid?
=> true
irb> person.errors.size
=> 0

8 在 View 中顯示驗證錯誤

建立 model 並新增驗證後,如果該 model 是透過建立的 Web 表單,您可能希望在以下情況之一時顯示錯誤訊息 驗證失敗。

因為每個應用程式處理這種事情的方式都不一樣,所以 Rails 是這樣做的 不包含任何 view helpers 來幫助您直接生成這些訊息。 然而,由於 Rails 為您提供了豐富的互動方法 一般驗證,您可以構建自己的。此外,當 生成一個 scaffold,Rails 會將一些 ERB 放入 _form.html.erb 它生成顯示該 model 上的完整錯誤列表。

假設我們有一個 model 儲存在一個名為的實例變數中 @article,它看起來像這樣:

<% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2>

    <ul>
      <% @article.errors.each do |error| %>
        <li><%= error.full_message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

此外,如果您使用 Rails 表單 helpers 生成表單,則當 欄位上發生驗證錯誤,它會在周圍生成一個額外的 <div> 入口。

<div class="field_with_errors">
  <input id="article_title" name="article[title]" size="30" type="text" value="">
</div>

然後,您可以隨意設定此 div 的樣式。預設的 scaffold 即 例如,Rails 生成添加了這個 CSS 規則:

.field_with_errors {
  padding: 2px;
  background-color: red;
  display: table;
}

這意味著任何有錯誤的欄位都會以 2 畫素的紅色邊框結束。

回饋

我們鼓勵您幫助提高本指南的品質。

如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。

您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。

如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue

關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區