在 Rails model 裡面,我們很常會定義所謂數據的「狀態」,比方說一個請假系統,光請假這個數據,可能他的狀態就有「待審核」、「已審核」、「已退回」、「已取消」。
雖然 Mysql 裡面也有 Enum 屬性可以設置,例如
CREATE TABLE person(
name VARCHAR(255),
gender ENUM('Male', 'Female')
);
既然一般資料庫都有 enum 屬性可以用,那為何我們很少知道有人這樣開發,其最大的問題在於這個 enum 會在建立這張表單時就塵埃落定,一旦後期需要增加一個狀態就意味著要在加一個字段,而且各類型的數據庫對於 enum 的方式處理不盡相同,會對 ORM 造成不小的麻煩,而 Rails 就除去了這個麻煩,讓數據庫只要單純的儲存 integer,並在 model 中來維護這些關係,並不直接使用數據庫的 enum,
在該 model 裡面,我們可以使用Constant(常數)去定義一個狀態的初始值
class LeaveEvent < ApplicationRecord
STATUS = %i(pending approved rejected canceled).freeze
end
然後在 migration 生成數據時,切記狀態 default 值一定要設定「pending」
class CreateLeaveEvents < ActiveRecord::Migration[5.0]
def change
create_table :leave_events do |t|
...
t.string :status, default: "pending"
...
end
end
end
這樣的作法是多數人在設定狀態會有的寫法,不過 Rails 在 4.1 的部分就針對了這樣常見的設定做了一個更棒的設置
ActiveRecord::Enum 的 Module
官方说明:
Declare an enum attribute where the values map to integers in the database, but can be queried by name.
如此一來,就可以用 enum
解決這個狀態設置的場景,更棒的是在資料庫裡面只需要儲存 integer
可以取代原本的 string
By the way 數據欄位型別也是會影響速度
Boolean > Integer > String > Date > Datetime
以請假系統來說,我們可以如此應用 Enum, 詳細參見官方文檔
先在 model 裡面宣告
class LeaveEvent < ActiveRecord::Base
enum status: { pending: 0, approved: 1 ,rejected: 2, canceled: 3 }
# or
enum status: [ :pending, :approved, :rejected, :canceled ]
end
宣告後,會幫你生出這些方法
leave_event.approved! # 將狀態改寫為 approved
leave_event.approved? # 檢查該狀態是否 approved
leave_event.status # => "approved" 輸出 String
leave_event[:status] # => "approved" 輸出 String(Rails 5 Feature)
leave_event.status = 1 # => "approved"
leave_event.status = 'approved' # => "approved"
leave_event.status = :approved # => "approved"
賦值時,以上三者等價
# 自動添加 Scope
LeaveEvent.approved # 等價於 LeaveEvent.where(status: 1)
# 自動添加 statuses(status 的複數) 的 Hash
LeaveEvent.statuses # => { "pending" => 0, "approved" => 1, "rejected" => 2, "canceled" => 3 }
這邊值得注意的一點是,通常這些狀態設定,幾乎都不會更動順序,一旦更動順序可能就會導致災難發生
比方說
# 原先定義的順序
enum status: [:temporary, :active, :deleted]
# 修改之後
enum status: [:temporary, :active, :waiting, :deleted]
# 如果這樣修改的話,以前的 deleted 的數據修改後就變成 waiting了
而且上述的例子中,容易讓人混淆順序,所以並不推薦使用
enum status: [:temporary, :active, :deleted]
而建議有良心的寫 code
{ temporary: 1, active: 2, deleted: 3 }
也因為 enum 會自帶一些 scope ,這部份也要注意有沒有與原先自己設定的 scope 衝突,像是如果使用 enum status: { none: 0, active: 1, deleted: 2}
那麼 Rails 自帶的 none
就會被覆蓋了哦
參考資源 - Creating Easy, Readable Attributes With ActiveRecord Enums - 译:使用ActiveRecord Enums创建简单易读的属性 - Rails 关于在 Rails Model 中使用 Enum (枚举) 的若干总结