本課時(shí)講解 Model 中的屬性校驗(yàn)方法,以及在頁面上顯示校驗(yàn)失敗信息。
我們將數(shù)據(jù)保存到數(shù)據(jù)庫的時(shí)候,可以有兩種數(shù)據(jù)校驗(yàn),一種是在數(shù)據(jù)庫中設(shè)定驗(yàn)證規(guī)則,一種是在程序中進(jìn)行校驗(yàn)。
Rails 為我們提供了方便的屬性校驗(yàn)。在 [4.2.1 兩個(gè) Gem] 一節(jié),我們介紹了ActiveRecord 中包含的兩個(gè) Gem,在數(shù)據(jù)查詢和關(guān)聯(lián)關(guān)系中,我們主要使用的是 arel。數(shù)據(jù)校驗(yàn)時(shí),我們使用的是 ActiveModel。
| 方法 | 含義 | 例子 |
|---|---|---|
| acceptance | 必須接受選項(xiàng),比如注冊條款(必須同意) | validates :terms_of_service, acceptance: true |
| validates_associated | 校驗(yàn)關(guān)聯(lián)資源,僅在關(guān)聯(lián)的一端使用即可,避免循環(huán)校驗(yàn) | [1] |
| confirmation | 填寫確認(rèn) | validates :email, confirmation: true |
| exclusion | 排除內(nèi)容,如某些保留關(guān)鍵詞不允許注冊使用 | validates :subdomain, exclusion: { in: %w(www us ca jp), message: "%{value} is reserved." } |
| format | 格式化,如郵件格式 | validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, message: "only allows letters" } |
| inclusion | 包含內(nèi)容,如特定的輸入內(nèi)容 | validates :size, inclusion: { in: %w(small medium large), message: "%{value} is not a valid size" } |
| length | 內(nèi)容長度 | validates :name, length: { minimum: 2 } [2] |
| numericality | 僅數(shù)字 | validates :points, numericality: true |
| presence | 必填,使用 blank? 方法判斷 |
validates :name, :login, :email, presence: true [3] [4] |
| absence | 必空,使用 present? 判斷 |
[5] |
| uniqueness | 唯一 | validates :email, uniqueness: true [6] |
注解:
[1]
class Library < ActiveRecord::Base
has_many :books
validates_associated :books
end
minimum,最短長度
[3] 也可以應(yīng)用在關(guān)聯(lián)關(guān)系上,如:
class LineItem < ActiveRecord::Base
belongs_to :order
validates :order, presence: true
end
為了保持內(nèi)存中引用相同地址,需要在 Order 上使用 inverse_of:
class Order < ActiveRecord::Base
has_many :line_items, inverse_of: :order
end
[4] 進(jìn)入 console,做個(gè)試驗(yàn):
false.blank?
=> true
true.blank?
=> false
所以,使用 presence 判斷 true/false 屬性時(shí),需要這樣使用:
validates :boolean_field_name, presence: true
validates :boolean_field_name, inclusion: { in: [true, false] }
validates :boolean_field_name, exclusion: { in: [nil] }
[5] 和 presence 一樣,需要使用 inverse_of 限定關(guān)聯(lián)關(guān)系,并且在判斷 true/false 時(shí):
validates :boolean_field_name, absence: true
validates :boolean_field_name, exclusion: { in: [true, false] }
[6] uniqueness 有兩個(gè)重要的選項(xiàng)。
scope,比如:
validates :number, uniqueness: { scope: : company_id }
保存到數(shù)據(jù)庫前,uniqueness 會(huì)先檢索數(shù)據(jù)庫是否已經(jīng)存在該字段的值,scope 可以使檢索時(shí)附帶一個(gè)字段,比如:不同的公司,可以有相同的訂單號,而同公司訂單號必須唯一。
validates :name, uniqueness: { case_sensitive: false }
默認(rèn)是 true,區(qū)分大小寫。改為 false,可不區(qū)分大小寫。
在檢驗(yàn)方法 validates 中,可以使用幾個(gè)選項(xiàng):
| 選項(xiàng) | 含義 | 例子 |
|---|---|---|
| allow_nil | 是否允許為 nil | validates :size, allow_nil: true |
| allow_blank | 是否允許為 blank?,為 false 時(shí),不可填寫 "", false, nil |
validates :title, allow_blank: true |
| message | 自定義錯(cuò)誤信息 | validates :subdomain, exclusion: { in: %w(www us ca jp), message: "%{value} 為保留關(guān)鍵詞" } |
| on | 選擇在 create 或 update 上使用校驗(yàn) | validates :email, uniqueness: true, on: :create |
| strict | 校驗(yàn)失敗時(shí)拋出異常,或自定異常類 | validates :name, presence: { strict: true } [1] |
注解
[1]
自定義異常類
class Person < ActiveRecord::Base
validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
Person.new.valid?
=> TokenGenerationException: Token can't be blank
在將數(shù)據(jù)保存到數(shù)據(jù)庫的時(shí)候,有些方法,會(huì)觸發(fā)校驗(yàn),有些則直接發(fā)送數(shù)據(jù)庫 sql 命令,不觸發(fā)校驗(yàn)。
! 結(jié)尾的方法,在校驗(yàn)失敗時(shí),會(huì)拋出異常。save(validate: false) 可以跳過 save 方法的校驗(yàn)。
我們可以在校驗(yàn)中增加 :if 或 :unless 條件判斷。
class Order < ActiveRecord::Base
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
這里使用的是方法判斷,也可以直接使用字符串,比如:
class Person < ActiveRecord::Base
??validates :surname, presence: true, if: "name.nil?"
end
或者一個(gè)代碼塊:
class Account < ActiveRecord::Base
validates :password, confirmation: true, unless: Proc.new { |a| a.password.blank? }
end
valid? 方法valid? 和 invalid? 方法會(huì)觸發(fā)校驗(yàn)。校驗(yàn)成功時(shí)返回 true,失敗時(shí),返回 false,并且將校驗(yàn)信息放入 errors 類。訪問 order.errors,返回的是 ActiveModel::Errors 實(shí)例,它的代碼在 這里。
校驗(yàn)失敗時(shí),model.errors 會(huì)保存入校驗(yàn)的屬性和失敗原因。我們可以通過幾個(gè)方法,從 errors 實(shí)例中拿到具體的信息。
% model.errors.messages
=> {:number=>["must be blank"]}
messages 方法返回的是 hash 結(jié)構(gòu)的信息,key 是校驗(yàn)的屬性。
% model.errors.full_messages
=> ["Number can't be blank", ...]
full_messages 方法返回 Array 結(jié)構(gòu)的完整錯(cuò)誤信息。這在資源編輯的 form 頁面,可以整體輸出錯(cuò)誤信息,不過它沒有具體到某個(gè)屬性上。對于某個(gè)屬性,我們可以使用 errors[:number] 來讀取:
% order.errors[:number]
=> ["can't be blank"]
在某些時(shí)候,我們需要添加自己的信息,可以使用:
order.errors.add(:number, "訂單號不能含有 !@#%*()_-+= 等字符")
如果添加的信息,并不一定是某個(gè)具體屬性,可以添加到errors 的 base 中:
order.errors.add(:base, "訂單格式不正確")
order.errors.clear,可以清理掉所有信息.
我們已經(jīng)注意到了,目前所有的校驗(yàn)信息都是英文的,雖然可以在自定義信息里寫入中文(Not Rails Style),但是我們可以利用 Rails 提供的 I18n gem,實(shí)現(xiàn)文本內(nèi)容的漢化。這包括異常信息。
我們先修改一下 I18n 文件加載地址,在 application.rb 文件里,我們找到這一段:
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**/*.{rb,yml}').to_s]
config.i18n.default_locale = :"zh-CN"
這樣會(huì)加載我們在 config/locales 中的全部語言包文件(注意,這里使用的是 **/*.{rb,yml})。
我們創(chuàng)建語言包,為了便于維護(hù),我在這里做了細(xì)分,大家可以在 這里 查看。
進(jìn)到終端里,測試下:
% product = Product.new
% product.valid?
=> false
% product.errors.full_messages
=> ["名稱不能為空字符"]
在后面的章節(jié)里,會(huì)專門講解 I18n 的問題,如果不像本例子中自己添加語言包,也可以安裝 rails-i18n 這個(gè) gem 來解決問題。
為了讓頁面集中的顯示錯(cuò)誤信息,我們在 form 中使用了局部模板,把校驗(yàn)失敗的內(nèi)容顯示在輸入框的頂部。
<% if @product.errors.any? %>
<div id="error_expl" class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title"><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h3>
</div>
<div class="panel-body">
<ul>
<% @product.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
</div>
<% end %>
full_messages 返回 Array 的校驗(yàn)信息,我們只需循環(huán)顯示即可。如果想在輸入框旁邊顯示信息,可以單獨(dú)讀取該屬性,比如 @product.errors[:name],可以放到一個(gè) jquery 的 tooltip 中。
不過,這種信息是要提交到服務(wù)器端處理后,才能顯示出來的。為了在頁面端就顯示校驗(yàn),我們還是需要 jQuery 插件的。
Form 校驗(yàn)的時(shí)候,有兩個(gè)插件較常用。
http://jqueryvalidation.org/ 是較常用的一個(gè),也很簡單,但是需要在頁面上顯示中文,還需要它的中文插件。
<%= javascript_include_tag 'spree/jquery.validate/localization/messages_zh' %>
中文語言包的源碼在[這里] (https://github.com/jzaefferer/jquery-validation/blob/master/src/localization/messages_zh.js)。
如果不需要校驗(yàn)具體信息,因?yàn)槲覀円呀?jīng)使用了 bootstrap 這個(gè)前端框架,所以我們可以使用它的表單校驗(yàn):http://bootstrapvalidator.com
它會(huì)按照 bootstrap 的方式,將輸入框加上圖標(biāo),使校驗(yàn)更加直觀。當(dāng)然,你還可以讀取具體的屬性信息,放到 bootstrap 的 tooltip 里。
和上一張的關(guān)聯(lián)關(guān)系一樣,shoulda-matchers 也提供了方便的校驗(yàn)測試框架。
describe Product do
it { should validate_presence_of(:name) }
end
現(xiàn)在我們給 Model 增加了越來越多的內(nèi)容,為了方便找到方法,我們可以對代碼進(jìn)行一個(gè)簡單的分割,這樣就不會(huì)在測試和對應(yīng)的業(yè)務(wù)代碼間切換浪費(fèi)時(shí)間了。
# extends ...................................................................
# includes ..................................................................
# security ..................................................................
# relationships .............................................................
# validations ...............................................................
# callbacks .................................................................
# scopes ....................................................................
# additional config .........................................................
# class methods .............................................................
# public instance methods ...................................................
# protected instance methods ................................................
# private instance methods ..................................................