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 不會執行 INSERT
或 UPDATE
操作。這避免了
在資料庫中儲存無效物件。您可以選擇具有特定的
在建立、儲存或更新物件時執行驗證。
有多種方法可以更改資料庫中物件的狀態。 有些方法會觸發驗證,但有些方法不會。這意味著它是 如果您不是,則可以將資料庫中的物件以無效狀態儲存 小心。
以下方法觸發驗證,並將物件儲存到 僅當物件有效時才使用資料庫:
create
create!
save
save!
update
update!
如果記錄無效,bang 版本(例如 save!
)會引發異常。
非 bang 版本不會:save
和 update
返回 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
實例化的物件不會報錯
即使它在技術上無效,因為驗證是自動執行的
僅當物件被儲存時,例如使用 create
或 save
方法。
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
選項接受 String
或 Proc
。
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、Proc
或 Array
。您可以使用 :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
和 :unless
與 Proc
物件相關聯
這將被呼叫。使用 Proc
物件使您能夠編寫一個
內聯條件而不是單獨的方法。此選項最適合
單襯。
class Account < ApplicationRecord
validates :password, confirmation: true,
unless: Proc.new { |a| a.password.blank? }
end
由於 Lambdas
是 Proc
的一種,所以它們也可以用來寫內聯
條件更短。
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 討論區。