Với những người đã và đang làm việc với Ruby on Rails, hẳn khái niệm Enum không còn gì xa lạ với các bạn. Enum được đưa vào Rails nhằm mục đích khiến code đọc hiểu dễ hơn, đẩy lùi những con số vô hồn và thay bằng các ngôn từ dễ hiểu.

Tuy nhiên Enum cũng có những vấn đề, mà nếu không biết và sử dụng không cẩn thận, chúng ta có thể đối mặt với những hiểm họa khôn lường.

Trong khuôn khổ bài viết này, mình sẽ trình bày về bản chất của Enum cũng như những lưu ý được đúc kết từ vài (chục) lần hiển thị lỗi 500 trên trang web.

Enum là gì?

Theo định nghĩa từ API Document của Rails thì :

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

Điều này có nghĩa là trường được định nghĩa là enum thông qua Rails sẽ có giá trị khi lưu trong DB là số, còn chúng ta có thể thông qua những dòng text đầy ý nghĩa để truy xuất chúng.

Default Value

Để hiểu hơn chúng ta hãy cùng xem qua một vài ví dụ.

Ta sẽ định nghĩa một Model có tên là Human trong Rails như sau :

class Human < ActiveRecord::Base
  enum status: [ :dead, :alive ]
end

Như các bạn thấy, class Human sẽ có một thuộc tính có tên là status, tương ứng với cột status trong database, trường này sẽ nhận 2 giá trị trạng thái tương ứng với 2 symbol đã định nghĩa là :dead:alive.

Với thiết lập như trên, Rails sẽ tự động gán giá trị 0 cho trạng thái :dead và 1 cho trạng thái :alive. Và migration để tạo bảng humen tương ứng với model Human sẽ như sau:

class CreateHumen < ActiveRecord::Migration
  def change
    create_table :humen do |t|
        t.string :name
        t.integer :status
    end
  end
end

Sau khi migrate và thử rails console, chúng ta sẽ thấy một vài điều như sau :

irb(main):024:0> h = Human.new
=> #<Human id: nil, name: nil, status: nil>
irb(main):025:0> h.dead?
=> false
irb(main):026:0> h.alive?
=> false
irb(main):027:0> 

Một instance Human được tạo ra sẽ được bổ sung 2 hàm dead?alive? tương ứng với 2 giá trị đã khai báo của enum.
Lúc mới khởi tạo, do giá trị status của instance đang là nil nên 2 hàm này đều trả về kết quả là false, tương ứng với ý nghĩa là người này ko sống cũng không chết (Dead or Alive, no I'm the other =)) )

Và đó là điểm nguy hiểm đầu tiên của enum. Nếu không khai báo giá trị mặc định cho trường dùng làm enum, thì giá trị của trường đó khi mới khởi tạo sẽ là nil và mọi hàm gán cho giá trị đó đều trả về False
Thử tưởng tượng bạn có 1 logic hiển thị dựa trên status như này :

render "giay_khai_sinh" if h.alive?
render "giay_bao_tu" if h.dead?

Và kết quả là sẽ không có cái giấy khai sinh hay báo tử nào được show ra cả, vì instance h đang không sống, cũng không chết (yaoming)

Nếu chúng ta đổi logic thành như này :

if h.alive?
  render "giay_khai_sinh"
else 
  render "giay_bao_tu"
end

hoặc như này :

if h.dead?
  render "giay_bao_tu"
else 
  render "giay_khai_sinh"
end

Thì mọi việc thậm chí còn TỆ hơn, khi mà giấy khai sinh hoặc giấy báo tử sẽ được show khi trạng thái của status là nil hoặc dead/alive. Điều này gây ra sự thiếu nhất quán trong logic, khi mà view có thể được show ra bởi 1 trong 2 loại giá trị, mà 1 trong 2 gía trị đó là cái mà chúng ta không mong muốn. Giả sử trong giấy báo tử hoặc giấy khai sinh có một vài trường được show hoặc được set dựa trên status dead hoặc alive của instance Human, khi đó giá trị sẽ không được thể hiện chính xác, thậm chí đôi khi là Something went wrong (yaoming)

Để khắc phục vấn đề này, điều đầu tiên mà tôi muốn khuyên các bạn là, với các trường sử dụng enum, hãy set giá trị mặc định cho nó và đừng bao giờ cho phép nó nil, tức là null: false. Chúng ta sẽ sửa lại migration như sau :

class CreateHumen < ActiveRecord::Migration
  def change
    create_table :humen do |t|
        t.string :name
        t.integer :status, null: false, default: 0
    end
  end
end

Chạy lại migration và test trên console xem nào :

irb(main):005:0> Human.new.dead?
=> true
irb(main):006:0> Human.new.alive?
=> false
irb(main):007:0> Human.new.status
=> "dead"
irb(main):009:0> Human.new
=> #<Human id: nil, name: nil, status: 0>

Lúc này trường status đã có giá trị mặc định là 0, do đó khi khởi tạo một instance Human, mặc định status là dead, khá an toàn để bạn thao tác.
Chúng ta để thêm null: false để tránh các trường hợp set giá trị nil mà vẫn lưu vào được DB như sau :

irb(main):013:0> h = Human.new
=> #<Human id: nil, name: nil, status: 0>
irb(main):014:0> h.status = nil
=> nil
irb(main):015:0> h.save
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "humen" ("status") VALUES (?)  [["status", nil]]
SQLite3::ConstraintException: NOT NULL constraint failed: humen.status: INSERT INTO "humen" ("status") VALUES (?)
   (0.0ms)  rollback transaction

Nhìn ở đoạn code trên, bạn có thể thấy 1 điều là : Chúng ta có thể set giá trị nil cho trường status. Do đó nếu ở DB ko có ràng buộc null: false, chúng ta có thể lưu giá trị nil cho trường status lúc nào tùy thích, và lúc này logic lại trở nên như trường hợp ở trên (facepalm). Vì thế, khi làm việc với các trường enum, nên set giá trị mặc định và không cho phép được nil

Added Functions

Vậy với các giá trị không phải nil nhưng nằm ngoài 2 giá trị deadalive thì sao? Thật may là Rails đã xử lý hộ chúng ta rồi :

irb(main):038:0> h.status = 1
=> 1
irb(main):039:0> h.alive?
=> true
irb(main):040:0> h.status = "alive"
=> "alive"
irb(main):041:0> h.alive?
=> true
irb(main):042:0> h.status = :alive
=> :alive
irb(main):043:0> h.alive?
=> true
irb(main):044:0> h
=> #<Human id: nil, name: nil, status: 1>

irb(main):032:0> h.status = "zombie"
ArgumentError: 'zombie' is not a valid status
irb(main):034:0* h.status = 2
ArgumentError: '2' is not a valid status

Một exception có tên là ArgumentError sẽ được throw ra khi bạn set giá trị status không phải là các giá trị của enum.
Ở ví dụ trên, bạn có thể thấy chúng ta có thể set giá trị là 1, :alive, "alive" đều được. Vậy các giá trị được set cho trường của enum sẽ bao gồm các loại sau :

  1. Giá trị số lưu trong DB : 0, 1
  2. String tương ứng với symbol của enum : dead, alive
  3. Symbol của enum : :dead, :alive

Ngoài các giá trị này, khi set các giá trị bất kỳ (ngoài giá trị nil đã nói ở trước), đều có exception ArgumentError. Vì thế để tránh sai sót khi set giá trị cho enum, ngoài việc set trực tiếp các bạn có thể dùm hàm alive! hoặc dead! như sau :

irb(main):046:0> h.alive!
   (0.1ms)  begin transaction
  SQL (0.2ms)  INSERT INTO "humen" ("status") VALUES (?)  [["status", 1]]
   (85.1ms)  commit transaction
=> true
irb(main):047:0> h.dead!
   (0.1ms)  begin transaction
  SQL (0.8ms)  UPDATE "humen" SET "status" = ? WHERE "humen"."id" = ?  [["status", 0], ["id", 2]]
   (94.9ms)  commit transaction
=> true

2 hàm trên tương đương với việc set h.status = 0 hoặc h.status = 1, tuy nhiên gọi bằng cách trên sẽ khiến code dễ đọc (sống dậy đi tên người kia, chết đi tên người kia =)) ) và không bị Exception nếu chẳng may gõ nhầm h.status = :death hoặc h.status = 11.

Update Enum

Tiếp theo, nếu sau này model Human của chúng ta mở rộng, tiến hóa thành chủng loài siêu nhân hoặc bị zombie hóa, lúc này trường status sẽ cần lưu thêm vài trạng thái nữa, ví dụ như :mutant hoặc :zombie, và chúng ta sẽ sửa lại model Human như sau :

class Human < ActiveRecord::Base
    enum status: [:dead, :alive, :mutant, :zombie]
end

Theo dự đoán của giới chuyên môn, 2 giá trị :mutant:zombie sẽ tương ứng với giá trị 2 và 3 trong database, chúng ta hãy đi kiểm tra điều này :

irb(main):050:0> Human.statuses
=> {"dead"=>0, "alive"=>1, "mutant"=>2, "zombie"=>3}
irb(main):051:0> h = Human.new
=> #<Human id: nil, name: nil, status: 0>
irb(main):052:0> h.status = :mutant
=> :mutant
irb(main):053:0> h.mutant?
=> true
irb(main):054:0> h
=> #<Human id: nil, name: nil, status: 2>
irb(main):055:0> h.status = 3
=> 3
irb(main):056:0> h.zombie?
=> true
irb(main):057:0> h
=> #<Human id: nil, name: nil, status: 3>
irb(main):058:0> h.mutant?
=> false
irb(main):059:0> h.dead?
=> false
irb(main):060:0> 

À quên chưa giới thiệu với các bạn, khi sử dụng enum, ngoài các hàm cho instance, Rails còn ưu ái cho thêm chúng ta 1 hàm cho lớp Human để liệt kê các status có thể có với tên là statuses (số nhiều của status).

Mọi thứ có vẻ khá êm xuôi khi cứ thêm status mới vào cuối array theo kiểu này. Tuy nhiên nếu có một đấng toàn năng nào đó lý sự rằng
:mutant > :alive > :zombie > :dead (dấu lớn hơn biểu thị rằng dạng sống phía tay trái cao cấp hơn tay phải) và bắt chúng ta sửa lại như sau :

class Human < ActiveRecord::Base
    enum status: [:mutant, :alive, :zombie, :dead]
end

Lúc này mọi thứ sẽ trở nên Khác, rất khác:

irb(main):076:0> Human.new.mutant?
=> true
irb(main):077:0> h = Human.new
=> #<Human id: nil, name: nil, status: 0>
irb(main):078:0> h.mutant?
=> true
irb(main):079:0> h.dead?
=> false
irb(main):080:0> h.save
   (0.1ms)  begin transaction
  SQL (0.1ms)  INSERT INTO "humen" DEFAULT VALUES
   (69.5ms)  commit transaction
=> true
irb(main):081:0> h.dead?
=> false
irb(main):082:0> h.status
=> "mutant"
irb(main):083:0> h.status = 3
=> 3
irb(main):084:0> h.status
=> "dead"
irb(main):085:0> h.dead?
=> true
irb(main):086:0> h.mutant?
=> false
irb(main):087:0> 

Instance Human lúc đầu sẽ mang giá trị mặc định là :mutant chứ ko phải là :dead nữa, các giá trị trong DB sẽ bị chuyển đổi như sau :

  • 0 - từ :dead thành :mutant - Người chết sống lại và thành siêu nhân
  • 1 - giữ nguyên :alive
  • 2 - từ :mutant thành :zombie - Siêu nhân trở thành zombie vật vờ =))
  • 3 - từ :zombie thành :dead - Zombie chết =))

Chúng ta có thể thấy logic cho trường status đã HOÀN TOÀN BỊ BIẾN ĐỔI, và điều này nếu xét theo tính logic thì tương đương với vài sự kiện to to như là thả bom hạt nhân, hồi sinh người chết, ...
Chỉ thay đổi thứ tự trong mảng, nhưng ảnh hưởng đến logic là cực kì lớn, do đó đây chính là điểm cần lưu ý với enum : KHÔNG THAY ĐỔI THỨ TỰ CÁC ENUM ĐÃ ĐƯỢC SỬ DỤNG TRƯỚC ĐÓ, NẾU KHÔNG LOGIC SẼ BỊ THAY ĐỔI THEO.

Để giải quyết / phòng tránh thảm họa này, thật may là Rails cho phép chúng ta set các giá trị status theo kiểu Hash, gồm key và value như sau :

class Human < ActiveRecord::Base
    enum status: {
        mutant: 2, 
        alive: 1,
        zombie: 3,
        dead: 0
    }
end

Lúc này logic sẽ được update lại như thưở ban đầu :

irb(main):092:0> h = Human.new
=> #<Human id: nil, name: nil, status: 0>
irb(main):093:0> h.dead?
=> true
irb(main):094:0> h.mutant!
   (0.1ms)  begin transaction
  SQL (0.2ms)  INSERT INTO "humen" ("status") VALUES (?)  [["status", 2]]
   (98.9ms)  commit transaction
=> true
irb(main):095:0> h
=> #<Human id: 5, name: nil, status: 2>
irb(main):096:0> h.zombie!
   (0.1ms)  begin transaction
  SQL (0.2ms)  UPDATE "humen" SET "status" = ? WHERE "humen"."id" = ?  [["status", 3], ["id", 5]]
   (82.2ms)  commit transaction
=> true
irb(main):097:0> h
=> #<Human id: 5, name: nil, status: 3>

Và như vậy là thế giới đã quay lại như nó vốn phải thế, xin chúc mừng các bạn :D

Select With Enum

Vậy là sau khi loài người đã được phân loại thành các trạng thái khác nhau, khi cần kiểm tra người sống, người chết, số siêu nhân số zombie, chúng ta cần làm thế nào. Theo như bản chất của enum là lưu giá trị số vào DB, cách thủ công và trực tiếp nhất sẽ là như sau :

# đếm số người chết
irb(main):112:0> Human.where(status: 0).count
   (0.2ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", 0]]
=> 4

#đếm số người sống
irb(main):113:0> Human.where(status: 1).count
   (0.1ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", 1]]
=> 0

#đếm số người đột biến
irb(main):114:0> Human.where(status: 2).count
   (0.1ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", 2]]
=> 0

#đếm số zombie
irb(main):115:0> Human.where(status: 3).count
   (0.1ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", 3]]
=> 1

Nhưng dùng where(status: <number>) như trên kia thì nhìn code chúng ta lại thấy toàn số vô nghĩa. Để phục vụ vấn đề này thì Rails đã cung cấp các hàm helper cho class Human như sau :

# đếm số người chết
irb(main):112:0> Human.dead.count
   (0.2ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", 0]]
=> 4

#đếm số người sống
irb(main):113:0> Human.alive.count
   (0.1ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", 1]]
=> 0

#đếm số người đột biến
irb(main):114:0> Human.mutant.count
   (0.1ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", 2]]
=> 0

#đếm số zombie
irb(main):115:0> Human.zombie.count
   (0.1ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", 3]]
=> 1

Chúng ta có 4 hàm dead, alive, mutant, zombie để chọn ra các human theo từng loại status. Bản chất của 4 hàm này chính là 4 scope, do đó chúng ta có thể nối thêm các scope khác để việc query thuận tiện hơn, ví dụ :

irb(main):121:0> Human.dead.where(id: 1)
  Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ? AND "humen"."id" = ?  [["status", 0], ["id", 1]]
=> #<ActiveRecord::Relation [#<Human id: 1, name: nil, status: 0>]>

Tuy nhiên, nếu trong trường hợp chúng ta ko muốn dùng các scope trên mà muốn query trực tiếp vào trường status, nhưng lại thích dùng giá trị text hoặc symbol cho code dễ hiểu như là : Human.where(status: :alive) hoặc Human.where(status: "alive"), bạn nghĩ kết quả sẽ như thế nào.
Cùng thử xem nhé

Với Rails 4 trở về trước

irb(main):124:0> Human.dead.where(status: "alive")
Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ? AND "humen"."status" = ?  [["status", 0], ["status", 0]]
=> #<ActiveRecord::Relation [#<Human id: 1, name: nil, status: 0>, #<Human id: 2, name: nil, status: 0>, #<Human id: 3, name: nil, status: 0>, #<Human id: 4, name: nil, status: 0>]>

irb(main):125:0> Human.dead.where(status: :alive)
Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ? AND "humen"."status" = ?  [["status", 0], ["status", nil]]
=> #<ActiveRecord::Relation []>

Cả 2 trường hợp đều ra kết quả không mong muốn.
Vì bản chất của trường sử dụng enum được lưu vào Database là số, nên khi dùng hàm lệnh where, giá trị được so sánh sẽ được convert về dạng number.
Trong trường hợp là string "alive", to_i sẽ ra 0
Trong trường hợp là symbol :alive, ko có hàm to_i nên sẽ trả về nil

Và kết quả là khi dùng "alive" lại trả về các bản ghi của status = 0 - tương đương với status là dead , và lỗi này khi tracking thì phải nói là vô cùng phiền phức.
Khi dùng :alive lại không trả về bản ghi nào do so sánh với nil.

Tương tự khi so sánh status với giá trị text hoặc symbol không phải là các giá trị của enum, theo luật convert ở trên kết quả cũng dễ đoán được phải không nào:

irb(main):135:0> Human.where(status: :alivvvv)
Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", nil]]
=> #<ActiveRecord::Relation []>

irb(main):136:0> Human.where(status: "alivvvv")
Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", 0]]
=> #<ActiveRecord::Relation [#<Human id: 1, name: nil, status: 0>, #<Human id: 2, name: nil, status: 0>, #<Human id: 3, name: nil, status: 0>, #<Human id: 4, name: nil, status: 0>]>

Do đó, thêm một nguyên tắc nữa là Vì trường dùng cho enum là số, nên không được so sánh giá trị đó với bất kỳ text hoặc symbol nào, tuyệt đối không

Vậy workaround trong trường hợp này là gì, khi muốn so sánh giá trị của status, ko dùng scope (để cho trường hợp query phức tạp cần xài Arel chẳng hạn) nhưng vẫn giữ được ngữ nghĩa.
Và đây là cách mà mình đã sử dụng :

irb(main):133:0> Human.where(status: Human.statuses[:alive])
Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", 1]]
=> #<ActiveRecord::Relation []>

irb(main):134:0> Human.where(status: Human.statuses["alive"])
Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", 1]]
=> #<ActiveRecord::Relation []>

Các bạn còn nhớ hàm Human.statuses ở trên chứ, xài nó trong where và câu lệnh sẽ trở nên có ý nghĩa hơn rất nhiều so với con số :D
Tuy nhiên cách này viết cũng hơi rườm rà, phải nhớ cả tên Class lẫn giá trị của enum, nên trong trường hợp bắt buộc thì mới cần dùng, còn không mình khuyên các bạn nên dùng scope sẵn có của enum mà Rails đã tạo ra.

Với Rails 5 trở đi

Ở ngay phía trên mình đã nói về việc chúng ta có thể gặp rắc rối nếu thực hiện query trên các trường dùng enum bằng symbolstring, với nguyên nhân chủ yếu là do convert các giá trị so sánh bằng hàm to_i.
Tuy nhiên, kể từ phiên bản Rails 5 trở đi, cơ chế query với trường enum đã được cập nhật, và việc query so sánh giá trị bằng string hay symbol đã trở nên khả thi.

irb(main):048:0> Human.where(status: 1)
Human Load (0.2ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", 1]]
=> #<ActiveRecord::Relation [#<Human id: 2, name: nil, status: "alive", created_at: "2017-05-08 02:15:57", updated_at: "2017-05-08 02:30:07">]>

irb(main):044:0> Human.where(status: :alive)
Human Load (0.2ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", 1]]
=> #<ActiveRecord::Relation [#<Human id: 2, name: nil, status: "alive", created_at: "2017-05-08 02:15:57", updated_at: "2017-05-08 02:30:07">]>

irb(main):045:0> Human.where(status: "alive")
Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", 1]]
=> #<ActiveRecord::Relation [#<Human id: 2, name: nil, status: "alive", created_at: "2017-05-08 02:15:57", updated_at: "2017-05-08 02:30:07">]>

irb(main):047:0> Human.where(status: Human.statuses[:alive])
Human Load (0.1ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", 1]]
=> #<ActiveRecord::Relation [#<Human id: 2, name: nil, status: "alive", created_at: "2017-05-08 02:15:57", updated_at: "2017-05-08 02:30:07">]>

Như ví dụ ở trên các bạn đã thấy, chúng ta có thể query trường status bằng số, symbol, string và giá trị trong mảng Human.statuses. Cập nhật này theo mình khá là hữu ích, vì nó đã giúp cho chúng ta có thể viết các đoạn code xử lý enum một cách ngắn gọn và dễ hiểu hơn rất nhiều - khi cho phép sử dụng symbol và string làm giá trị so sánh với enum. Bản chất ở đây là khi biến đổi thành câu query, hàm to_i đã được thay thế bằng logic đối chiếu giá trị của string/symbol trong bảng hash định nghĩa các giá trị enum.

Tuy nhiên có một lưu ý, với các string và enum nằm ngoài các giá trị đã thiết lập, lúc này Rails sẽ không báo lỗi và chỉ trả về kết quả rỗng như sau:

irb(main):056:0* Human.where(status: :alivv)
 Human Load (0.2ms)  SELECT "humen".* FROM "humen" WHERE "humen"."status" = ?  [["status", :alivv]]
=> #<ActiveRecord::Relation []>

irb(main):057:0> Human.where(status: :alivv).count
  (0.2ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", :alivv]]
=> 0

irb(main):058:0> Human.where(status: "alivv").count
  (0.2ms)  SELECT COUNT(*) FROM "humen" WHERE "humen"."status" = ?  [["status", "alivv"]]
=> 0

Do đó, khi sử dụng string/symbol cho trường enum, các bạn cần đảm bảo giá trị truyền vào là đúng như những gì mình đã thiết lập trong enum của model, đồng thời nên có các test case và validate với các giá trị sẽ truyền vào để tránh những lỗi tiềm ẩn có thể xảy ra sau này.

Tổng kết

Như vậy là mình đã đi qua một loạt các điểm cần lưu ý khi sử dụng Enum, sau đây mình xin tổng kết lại, hy vọng các bạn sẽ hiểu thêm được phần nào về cơ chế hoạt động của tính năng này và lựa chọn sử dụng chúng một cách tối ưu nhất trong dự án :

  1. Trường lưu trữ Enum không nên để nullable và luôn có giá trị mặc định
  2. Dùng các hàm helper (với tên key và dấu ? hoặc ! ) để kiểm tra và set giá trị cho trường enum
  3. Luôn set giá trị key - value khi dùng enum để tránh trường hợp cập nhật logic mà không kiểm soát được giá trị lưu vào DB
  4. Các hàm helper của enum được tạo cho class và instance, do đó các key của enum trong cùng 1 class không được trùng nhau (kể cả giữa 2 trường enum khác nhau)
  5. Kết quả trả về của hàm có tên giống với tên trường enum luôn trả về string hoặc nil (trong trường hợp này là Human.new.status
  6. Khi select các bản ghi, với các trường enum, với Rails 4 trở về trước, không được so sánh với bất kỳ text hoặc symbol nào, tuyệt đối không, chỉ có thể dùng số hoặc thông qua Hash chứa các enum. Với Rails 5, có thể sử dụng string/symbol bình thường nhưng cần lưu ý validate giá trị truyền vào để tránh các rắc rối có thể xảy ra và khó debug khi truyền sai giá trị.