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](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html) 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 : ```ruby 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` và `: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: ```ruby 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 : ```ruby 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?` và `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 : ```ruby 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 : ```ruby if h.alive? render "giay_khai_sinh" else render "giay_bao_tu" end ``` hoặc như này : ```ruby 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 : ```ruby 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 : ```ruby 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 : ```ruby 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ị `dead` và `alive` thì sao? Thật may là `Rails` đã xử lý hộ chúng ta rồi : ```ruby 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 : ```ruby 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 : ```ruby 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` và `: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 : ```ruby 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 : ```ruby 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**: ```ruby 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 : ```ruby 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 : ```ruby 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 : ```ruby # đế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 : ```ruby # đế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ụ : ```ruby 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 ```ruby 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: ```ruby 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 : ```ruby 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 **symbol** và **string**, 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. ```ruby 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: ```ruby 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ị.