Nic Lin's Blog

喜歡在地上滾的工程師

使用 AvtiveRecord:Enum 建立易讀的狀態屬性

在 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 (枚举) 的若干总结

comments powered by Disqus