在 Active Record 裡,有一個滿常見的功能,Enum,這功能該如何使用,今天就來點 Enum 吧!
Enum(Enumeration的縮寫)稱為列舉,是一種程式設計中常見的資料型別,
用於定義一組具有固定名稱的整數常數。
這些名稱通常用作代表特定狀態、選項、或類別的符號,使我們在程式上更易於閱讀、理解和維護。
讓我們來看看 Rails 裡該如何使用 Enum 吧!
在 Rails 中,Enum(列舉)是一個用於定義 Model 屬性的機制。
可以使用 Enum 來將整數映射到易於理解的名稱(自定義名稱),以增強代碼的可讀性。
首先,我們已經建立好 Order
Model,
現在我想針對一張訂單可能會有的狀態新增一個欄位到 orders
資料表上。
一張訂單可能有這四種狀態:pending
、shipped
、delivered
和 canceled
,
可以在建立狀態欄位上使用整數的資料型態來設置:
rails g migration AddStatusToOrder status:integer
點進去剛剛新增的 migration 檔案:
class AddStatusToOrder < ActiveRecord::Migration[7.0]
def change
add_column :orders, :status, :integer
end
end
如果想設定預設值為 0
,可以加上 default: 0
class AddStatusToOrder < ActiveRecord::Migration[7.0]
def change
add_column :orders, :status, :integer, default: 0
end
end
接著,我們到 Order
Model,使用 Enum 來定義狀態:
# app/models/order.rb
class Order < ApplicationRecord
enum :status, [:pending, :shipped, :delivered, :canceled]
end
%i()
format# app/models/order.rb
class Order < ApplicationRecord
enum :status, %i(pending shipped delivered canceled)
end
當我們使用陣列的方式去定義時,pending
、shipped
、delivered
和 canceled
,
將會被映射到 0
、1
、2
和 3
(陣列的第一個元素 index 為 0,以此類推)。
# app/models/order.rb
class Order < ApplicationRecord
enum :status, {
pending: 0,
shipped: 1,
delivered: 2,
canceled: 3
}
end
# app/models/order.rb
class Order < ApplicationRecord
enum :status, {
pending: '待處理',
shipped: '已發貨',
delivered: '已送達',
canceled: '已取消'
}
end
在使用上,比較建議用 Hash 的方式去定義,
因為當使用 Array 定義 Enum 時,資料庫會存取相應的 index 值,當之後要更改現有 Enum 的順序上會導致問題,因為資料庫裡的值可能與新的順序不符。
因此,當使用 Hash 定義,因為是自定義值,不是依賴於 index 值。當要更改 Enum 的順序時,只需更新 Enum 的 Hash 自定義值,而不會影響現有資料的整合性。
在 Model 定義好 Enum 之後,我們要如何使用呢?
statuses
方法透過剛剛的 Hash 定義,我們便可以用 Order.statuses
得到一個 Hash 如下:
# app/models/order.rb
class Order < ApplicationRecord
enum :status, {
pending: 0,
shipped: 1,
delivered: 2,
canceled: 3
}
end
Order.statuses
=> {"pending"=>0, "shipped"=>1, "delivered"=>2, "canceled"=>3}
Order.statuses[:pending] or Order.statuses["pending"]
=> 0
pending
, shipped
, delivered
, canceled
方法透過 Order.pending
, Order.shipped
, Order.delivered
, Order.canceled
,
這些方法用於查詢具有相應狀態的訂單記錄。Order.pending
將返回所有狀態為 "pending" 的訂單。
Order.pending
Order Load (0.3ms) SELECT "orders".* FROM "orders" WHERE "orders"."status" = ? [["status", 0]]
=>
[#<Order:0x0000000110836420
id: 3,
order_name: "20231010001",
created_at: Tue, 10 Oct 2023 06:17:08.953478000 UTC +00:00,
updated_at: Tue, 10 Oct 2023 06:17:08.953478000 UTC +00:00,
status: "pending">,
#<Order:0x00000001108362e0
id: 4,
order_name: "20231010002",
created_at: Tue, 10 Oct 2023 06:17:17.280539000 UTC +00:00,
updated_at: Tue, 10 Oct 2023 06:17:17.280539000 UTC +00:00,
status: "pending">,
#<Order:0x00000001108361a0
id: 5,
order_name: "20231010001",
created_at: Tue, 10 Oct 2023 07:12:39.204159000 UTC +00:00,
updated_at: Tue, 10 Oct 2023 07:12:39.204159000 UTC +00:00,
status: "pending">]
status
方法status
方法是一個可以用在知道一個實體變數的狀態方法:
建立一張新的 order001 之後,可以用 order001.status
,
剛剛因為有設定 default: 0
,所以結果會是 "pending"。
order001 = Order.create(order_name: "20231010001")
order001.status
=> "pending"
pending?
, shipped?
, delivered?
, canceled?
方法透過自定義的名稱加上 ?
,可以檢查是否為某種狀態:
order001 = Order.create(order_name: "20231010001")
order001.pending?
=> true
order001.shipped?
=> false
order001.delivered?
=> false
order001.canceled?
=> false
pending!
, shipped!
, delivered!
, canceled!
方法透過自定義的名稱加上 !
,可以更新狀態:
order001 = Order.create(order_name: "20231010001")
order001.shipped!
TRANSACTION (0.1ms) begin transaction
Order Update (1.4ms) UPDATE "orders" SET "updated_at" = ?, "status" = ? WHERE "orders"."id" = ? [["updated_at", "2023-10-10 07:36:12.025293"], ["status", 1], ["id", 3]]
TRANSACTION (1.2ms) commit transaction
=> true
order001.shipped?
=> true
order001.pending?
=> false
另外,我們也可以使用:order001.update(status: :shipped)
order001.update(status: :shipped)
TRANSACTION (0.1ms) begin transaction
Order Update (0.8ms) UPDATE "orders" SET "updated_at" = ?, "status" = ? WHERE "orders"."id" = ? [["updated_at", "2023-10-10 07:38:30.268312"], ["status", 1], ["id", 3]]
TRANSACTION (1.7ms) commit transaction
=> true
order001.shipped?
=> true
order001.pending?
=> false
但是相較 !
,這寫法比較冗長,我比較想要快速一點點,通常我都會用第一種方式去更新!
在 Enum 還可以使用 prefix
和 suffix
選項來控制生成的方法的前綴和後綴,以避免命名衝突。
prefix(前綴):
class Order < ApplicationRecord
enum status: { pending: 0, shipped: 1, delivered: 2, canceled: 3 }, prefix: true
end
生成的方法將具有前綴 status_
: status_pending
、status_shipped
、status_delivered
、status_canceled
。
suffix(後綴):
class Order < ApplicationRecord
enum status: { pending: 0, shipped: 1, delivered: 2, canceled: 3 }, suffix: true
end
生成的方法將具有後綴 _status
: pending_status
、shipped_status
、delivered_status
、canceled_status
。
今天就到這啦!我們下篇見~
參考資料:
文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)