Rails 6.1's ActiveModel Errors 介面大翻新
The English version of this post is available here
Rails 6.1 預計今年會推出,其中包含了 ActiveModel Errors 的大改變。我想來解釋一下改變的原因,以及開發者該怎樣做準備。
每個錯誤都被包成一個物件
當 model attribute 有錯誤資料時,呼叫 valid?
會在 errors.messages
這個 hash 中產生每個 attribute 相對應的錯誤訊息。不過在 Rails 5.0 開始,又新增了另一份叫做 details
的 hash,讓開發者還能取得更詳細的錯誤資訊。
這兩個hash應該要同步一對一,但是要把messages中某訊息對應到details是一件很瑣碎的工作。我們必須記得array index後才能在另一邊取得資料。這兩個 hash 也會在某些特定狀況變得不一致。有學過物件導向的大家應該都能了解資料不封裝的話,資料一致性就容易被破壞。所以在 Rails 6.1 ,我們把單一錯誤的所有資訊都封裝在一個物件中。
現在book.errors
其實是一陣列的Error
物件.
大改變通常就會伴隨著deprecation或是需要開發者修復的breaking change,以下我會把最重要的改變列出來:
如何更新?
message
跟 details
原本 messages
, full_messages
and details
這些方法都是一次提供所有錯誤資訊。現在新的 Error
物件只提供關於自己的資料:
e = ActiveModel::Error.new(book, :title, :too_long, limit: 1000)
e.message # 'is too long (maximum is 1000 characters)'
e.full_message # 'Title is too long (maximum is 1000 characters)'
e.detail # { limit: 1000, error: :too_long }
Enumeration 方法
原本 errors
運作類似 hash,比如說我們可以:
book.errors.each do |attribute, error_message|
puts attribute
puts error_message
end
為了相容性,在 Rails 6.1 中,上面的程式會出現 deprecation 警告。不過,要是 block 參數不是兩個而是只有一個時,列舉(enumeration)的方法就會 yield 出 Error 物件:
book.errors.each do |error|
puts error.attribute
puts error.message
end
有些列舉方法是沒有 block 的,如 first
,這些方法以前會回傳只有一個陣列,現在則是會回傳 Error
物件。這是breaking change,要是你的網頁有用到的話,麻煩更改一下。
避免直接更改 hash
以往,你可以直接插入新的錯誤訊息到 hash 中:
book.errors[:title] << 'is not interesting enough.'
book.errors[:title].clear
現在這些操作會給你 deprecation 警告,因為在未來,這些 hash 是每次呼叫時動態產生的,而不再是資料存放的源頭。要操作的話,請使用 add
方法或其他 Array 方法:
book.errors.add(:title, 'is not interesting enough.')
book.errors.delete_all { |error| error.type == :too_long }
以下的資料更改未來會沒有作用:
errors
本身類似 hash 的操作 (e.g.errors[:foo] = 'bar'
)errors#messages
回傳的 hash (e.g.errors.messages[:foo] = 'bar'
)errors#details
回傳的 hash (e.g.errors.details[:foo].clear
)
消除類似hash的介面
因為我們要把errors變成陣列,所以類hash的介面之後也會被移除,像是:
errors#slice!
errors#values
errors#keys
其他
errors#to_xml
會被移除errors#to_h
會被移除,但是可以用errors#to_hash
替代。
新東西
新的where
方法能用來尋找特定的錯誤。要注意的是它跟你熟悉的 ActiveRecord#where 有一點不同,我們可以依照以下的參數做篩選:
- model attribute (Symbol, 必須)
- error type (Symbol, 非必須)
- options (hash, 非必須)
篩選是依照有提供的參數:
book.errors.where(:name) # => 所有跟 name 相關的錯誤
book.errors.where(:name, :too_short) # => 所有 name 錯誤中是 too_short 類的錯誤
book.errors.where(:name, :too_short, minimum: 2) # => 所有 name 錯誤中是 too_short 類的錯誤,而且最低要求是 2
新的import
方法是用來匯入並包裝其他地方的錯誤。這對association或是巢狀service object很有幫助,未來應該能開啟新的應用方向。原本的merge!
已經使用import
了。
delete
方法以往只能刪除所有跟某 attribute 相關的錯誤。以後它也能做出更細緻的篩選,比如說只刪除 name 錯誤中 too_short 錯誤。
結論
若你有興趣,可以閱讀原始的 pull request。你也可以閱讀官方API文件。
Rails 6.1 還沒正式發表,這篇又是照我一年前的記憶寫的,所以有些錯誤。在此先說聲抱歉。
寫這篇是希望大家在 6.1 塵埃落定之前提出建議,以便作出修正或是減少升版號的痛苦。所以歡迎大家提供自己的想法。感謝~