iT邦幫忙

2023 iThome 鐵人賽

DAY 26
0
Software Development

從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!系列 第 26

Day 26 - 理解 Ruby on Rails,Active Record Query - Enum 是什麼?

  • 分享至 

  • xImage
  •  

在 Active Record 裡,有一個滿常見的功能,Enum,這功能該如何使用,今天就來點 Enum 吧!

Enum 是什麼?

Enum(Enumeration的縮寫)稱為列舉,是一種程式設計中常見的資料型別,
用於定義一組具有固定名稱的整數常數。
這些名稱通常用作代表特定狀態、選項、或類別的符號,使我們在程式上更易於閱讀、理解和維護。

讓我們來看看 Rails 裡該如何使用 Enum 吧!

Enum in Ruby on Rails

在 Rails 中,Enum(列舉)是一個用於定義 Model 屬性的機制。
可以使用 Enum 來將整數映射到易於理解的名稱(自定義名稱),以增強代碼的可讀性。

首先,我們已經建立好 Order Model,
現在我想針對一張訂單可能會有的狀態新增一個欄位到 orders 資料表上。
一張訂單可能有這四種狀態:pendingshippeddeliveredcanceled

可以在建立狀態欄位上使用整數的資料型態來設置:

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

如何在 Model 定義 Enum?

接著,我們到 Order Model,使用 Enum 來定義狀態:

在定義上可以使用多種方式,來看看有哪些寫法吧!

  • Array
# 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

當我們使用陣列的方式去定義時,pendingshippeddeliveredcanceled
將會被映射到 0123(陣列的第一個元素 index 為 0,以此類推)。

  • Hash
    除了使用 Array,我們還可以使用 Hash 去設定,並為每個值自定義整數或名稱去對應:
# 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 自定義值,而不會影響現有資料的整合性。

Enum 在 Rails 如使用?

在 Model 定義好 Enum 之後,我們要如何使用呢?

Order Model 擁有新的方法了!

透過複數型來抓取整個定義好的狀態 - 使用 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">] 

透過建立一張新的 order - 使用 status 方法

status 方法是一個可以用在知道一個實體變數的狀態方法:
建立一張新的 order001 之後,可以用 order001.status
剛剛因為有設定 default: 0,所以結果會是 "pending"。

order001 = Order.create(order_name: "20231010001")
order001.status
=> "pending" 

確認 order 狀態 - 使用 pending?, shipped?, delivered?, canceled? 方法

透過自定義的名稱加上 ?,可以檢查是否為某種狀態:

order001 = Order.create(order_name: "20231010001")
order001.pending?
=> true 
order001.shipped?
=> false 
order001.delivered?
=> false 
order001.canceled?
=> false 

更新 order 狀態 - 使用 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 

但是相較 !,這寫法比較冗長,我比較想要快速一點點,通常我都會用第一種方式去更新!

prefix, suffix

在 Enum 還可以使用 prefixsuffix 選項來控制生成的方法的前綴和後綴,以避免命名衝突。

  • prefix(前綴)

    class Order < ApplicationRecord
      enum status: { pending: 0, shipped: 1, delivered: 2, canceled: 3 }, prefix: true
    end
    

    生成的方法將具有前綴 status_status_pendingstatus_shippedstatus_deliveredstatus_canceled

  • suffix(後綴)

    class Order < ApplicationRecord
      enum status: { pending: 0, shipped: 1, delivered: 2, canceled: 3 }, suffix: true
    end
    

    生成的方法將具有後綴 _statuspending_statusshipped_statusdelivered_statuscanceled_status

今天就到這啦!我們下篇見~


參考資料:

文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)


上一篇
Day 25 - 理解 Ruby on Rails,Active Record Query - Scope 是什麼?
下一篇
Day 27 - 理解 Database - 資料庫是什麼?
系列文
從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言