[Rails] 搞懂 ActiveRecord::Enum

來自 Ruby china 精華帖

关于在 Rails Model 中使用 Enum (枚举) 的若干总结
在我使用 enum 時東找西找,挖掘一些坑
裡面提到枚舉這個詞,就是指 enum
看到該篇內容很精闢,提到很多想整理的重點
所以也筆記一份,至於詳情大家可以去看該篇文章

Enum 就是 Rails 用來消滅魔鬼數字的工具。

代碼中,以數字方式去表示數據狀態,導致代碼可讀性被破壞,這樣的數字被稱為『 魔鬼數字 』。
官方說明 enum:

Declare an enum attribute where the values map to integers in the database, but can be queried by name.

給數據庫中的整型字段聲明一個一一對應的 enum(枚舉) 屬性值,可以使用該字面作為查詢。字面

ActiveRecord::Enum 與 Mysql 的 Enum 有何不同

Enum 是為了解決數據庫問題,當然數據庫本身也有該功能:
Mysql 為例子:

1
2
3
4
CREATE TABLE person(
name VARCHAR(255),
gender ENUM('Male', 'Female')
);

這樣就設置了 gender 的 enum 字段

1
2
3
4
5
{
NULL: NULL,
1: 'Male',
2: 'Female'
}

ActiveRecord::Enum 在實現上,並不直接使用數據自身的 enum,僅用普通的 Integer 来做存儲。避免了 enum 屬性變動時會修改數據庫結構的問題。

ActiveRecord::Enum 的使用

1
2
3
4
5
6
7
8
9
10
11
# Migration
create_table :conversations do |t|
t.integer :status, default: 0
end

# Model
class Conversation < ActiveRecord::Base
enum status: { active: 0, waiting: 1, archived: 2 }
# or, but not recommended
enum status: [ :active, :waiting, :archived ]
end

聲明之後,會多出以下一些輔助方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
conversation.active! # 改寫狀態為 active
conversation.active? # 檢查狀態是否為 active

conversation.status # => "active" 輸出為字面量
conversation[:status] # => 0 輸出為數字鍵值(僅 Rails 4.x的版本)


conversation.status = 2 # => "archived"
conversation.status = "archived" # => "archived"
conversation.status = :archived # => "archived" 赋值時,三者等值

# 自動添加 Scope
Conversation.active # 等同於 Conversation.where(status: 0)

# 獲得一個名為 statuses 的 HashWithIndifferentAccess
Conversation.statuses # => { "active" => 0, "waiting" => 1, "archived" => 2 }
Conversation.statuses[:active] # => 0
Conversation.statuses["archived"] # => 2

使用注意事項

  1. 不要使用數據庫的 enum

  2. 不要使用數組預設定義 enum

    1
    enum status: %i[active waiting archived]
  3. enum 字段赋值時,會做數據驗證

  4. enum 的字面量要注意避开 model 已有的 method_names/attribute_names

  5. 建議给 enum 字段添加默认值

    1
    2
    3
    create_table :conversations do |t|
    t.integer :status, limit: 2, default: 0, null: false
    end
  6. 建議添加新属性时,寫 migration

    • 檢測當前數據庫中新加的值是否已经被佔用
    • 更新數據庫字段的 comment
    1
    2
    3
    4
    5
    6
    class AddDeletedStatusToConversations < ActiveRecord::Migration[5.1]
    def up
    raise "The value of deleted status has already been taken." if Conversation.deleted.count.positive?
    change_column :conversations, :status, :integer, default: 0, null: false, comment: "0 - active, 1 - waiting, 2 - archived, 3 - deleted"
    end
    end
  7. 建議數據庫字段不一定非得是 Integer

    • 我的認為是沒特殊需求,以 integer 主佳
  8. 结合 I18n 做出多語系

  9. 請升级到 Rails 5 以上的版本

  10. 注意手動設置 table_name 並做關連查詢,會有的坑,是 Rails 5.x 的專屬問題

ActiveRecord::Enum 在 Rails 5.0+ 中的新特性

主要的改進有:

  1. where 查詢支持使用字面量做查詢條件
  2. conversation[:status] 與 conversation.status 返回值一致,都是字面量
  3. 增加了兩个可選參數 _prefix 和 _suffix
# enum, rails

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×