linjiahung avatar

[Rails]Model驗證中常見的重要觀念

linjiahung

Published: 01 Jun 2018 › Updated: 01 Jun 2018[Rails]Model驗證中常見的重要觀念

[Rails]Model驗證中常見的重要觀念


一般來說,將資料存入資料庫前,為了確保資料的有效性,通常都會透過驗證來確保只有有效的資料才能存入資料庫。

雖然前端通常也會透過HTML和JS進行驗證,但難保不會有人關掉JS、會自行重新建立一組相似的表單。

而在Model層級驗證資料是最好的,只有通過驗證的資料方可存入資料庫。因為在 Model 層面驗證,不需要考慮資料庫的種類、無法在用戶端(瀏覽器)跳過驗證、且更容易測試與維護。

以下會針對一些驗證常見的重要觀念解釋。像是觸發驗證時機、會與不會觸發驗證的方法、如何手動加入錯誤訊息等等。

驗證觸發時機

Rails的驗證基本上只會在物件要被儲存、或是更動到資料庫時才會被觸發。

也就是說,當我們新增了一個Product的物件,但是還沒存到資料庫時,是不會觸發驗證的。因為這個時候此物件還不屬於資料庫,並不會觸發驗證。

如果我們想知道一個物件是否已經被存入資料庫中,可以透過new_record?這個方法。

product = Product.new
product.new_recored?
=> true
product.save
product.new_record?
=> false

會觸發驗證的方法

以下幾個方法會觸發驗證

create
create!
save
save!
update
update!

另外值得注意的是,使用有Bang(!)的方法時,如果資料無效會拋出Exception(異常);沒有Bang的則不會

不會觸發驗證的方法

以下的方法不會觸發驗證,將資料存入資料庫的時候不會考慮資料的有效性

decrement!
decrement_counter
increment!
increment_counter
toggle!
touch
update_all
update_attribute
update_column
update_columns
update_counters

自行觸發驗證

如果平時要自己觸發驗證,而不想要將資料存到資料庫中,可以透過valid?或是invalid?來觸發。

如果資料是有效的,valid?會回傳true;反之亦然。而invalid?的情況跟valid?完全相反。

errors(驗證錯誤訊息)

errors這個方法會回傳一個ActiveModel::Errors 類別的實體,包含了所有的錯誤。屬性名稱為鍵,值為由錯誤訊息字串組成的陣列。

p = Product.new
p.errors
=> #<ActiveModel::Errors:0x007faae10f6368 @base=#<Product id: nil user: template: created_at: updated_at:>, @messages={}, @details={}>

errors.messages(得到所有錯誤訊息)

當驗證物件出現錯誤時,所有的錯誤訊息可以在使用errors.message這個方法來取得。

class Product < ActiveRecord::Base
  validates :title, presence: true
  validates :sku, length: { minimum: 2 }
end

p = Product.create(sku: "ab")
(0.1ms)  begin transaction
(0.2ms)  rollback transaction
=> #<Product id: nil title: sku: template: created_at: updated_at:>

p.errors.messages
=> {:title=>["can't be blank"], :sku=>["is too short (minimum is 4 characters)"]}

需要注意的是,當你執行new的方法產生實體時,即使有錯誤也不會出現在錯誤訊息中,因為new並不觸發驗證。

class Product < ActiveRecord::Base
  validates :title, presence: true
end

product = Product.new
product.errors.messages
=> {}

product.valid? # => false
product.errors.messages
=> {:title=>["can't be blank"]}

errors[:attribute](得到特定屬性的錯誤訊息)

errors[:attribute]用來得到特定屬性的錯誤訊息。如果該屬性沒有錯誤訊息,則回傳空陣列

class Product < ActiveRecord::Base
  validates :title, presence: true
  validates :sku, length: { minimum: 2 }
end

p = Product.create(title: "books", sku: "ab")
p.errors[:sku]
=> ["is too short (minimum is 4 characters)"]
p.errors[:title]
=> []

errors.add(手動加入特定屬性的錯誤訊息)

平時在驗證時如果物件無效,會產生預設的錯誤訊息。如果我們想要自己手動加入針對特定屬性的錯誤訊息,我們可以過erros.add這個方法幫我們達成目的。

使用方法是第一個參數為屬性、第二個參數為手動加入的錯誤訊息。

以下我們會透過自行設定一個驗證方法來呈現。

class Product < ApplicationRecord
  validate :title_validator

  private
    def title_validator
      unless title.present?
        errors.add(:title, "Title不能是空的喔!")
      end
    end
end
product = Product.create
(0.1ms)  begin transaction
(0.1ms)  rollback transaction
 => #<Order id: nil title: created_at: updated_at:>
product.errors.messages
 => {:title=>["Title不能是空的喔!"]}

又或者你可以透過<<將錯誤訊息加入屬性中

class Product < ApplicationRecord
  validate :title_validator

  private
    def title_validator
      unless title.present?
        errors[:title] << "Title不能是空的喔!"
      end
    end
end

product = Product.create
(0.1ms)  begin transaction
(0.1ms)  rollback transaction
=> #<Order id: nil title: created_at: updated_at:>
product.errors.messages
=> {:title=>["Title不能是空的喔!"]}

errors[:base](為整個物件加入錯誤訊息,不針對某個屬性)

如果我們想為整個物件加入錯誤訊息,可以透過errors[:base]來加入。你同樣可以使用add<<的方式來達成。

class Product < ApplicationRecord
  validate :title_validator

  private
    def title_validator
      unless title.present?
        errors[:base] << "測試錯誤訊息")
        # errors.add(:base, "測試錯誤訊息")
      end
    end
end

product = Product.create
(0.1ms)  begin transaction
(0.1ms)  rollback transaction
=> #<Order id: nil title: created_at: updated_at:>
product.errors[:base]
=> ["測試錯誤訊息"]

errors.clear(清除所有錯誤訊息)

使用errors.clear 方法可以清除 errors 集合裡的所有錯誤。不過這個過程並不涉及任何驗證過程,也不會改變一個物件的有效性。

當下一次觸發驗證時,如果有錯誤訊息仍舊會重新填入errors集合。

class Product < ActiveRecord::Base
  validates :title, presence: true
  validates :sku, length: { minimum: 2 }
end

p = Product.create(title: "books", sku: "ab")
p.errors.messages
=> {:sku=>["is too short (minimum is 4 characters)"] }
p.errors.clear
=> {}
p.errors.messages
=> {}
p.save
p.errors.messages
=> {:sku=>["is too short (minimum is 4 characters)"] }

自訂驗證方法

我們在上面的文章中便已經有實作過自訂驗證方法,一般的做法是定義一個private method,並透過validate來註冊這個method

class Invoice < ActiveRecord::Base
  validate :expiration_date_cannot_be_in_the_past
    
    
  private
    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
end

常見輔助驗證方法

在Rails中有許多常見的輔助驗證方法,像是

acceptance
presence
congirmation
format
...等等

看更多輔助驗證方法可以參考Validation Helpers

參考資料
Active Record Validations
為你自己學為你自己學 Ruby on Rails — Model 驗證及回呼

Leave [Rails]Model驗證中常見的重要觀念 to:

Written by

Programmer, writer, lifelong learner

Read more #cn posts


Best Posts From linjiahung

We have not curated any of linjiahung's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.

More Posts From linjiahung