Array syntax for enum backed by string column

Scenario

  1. Create a Books table with a string column for its cover type.
# db/schema.rb
ActiveRecord::Schema.define(version: 2021_08_19_014728) do
  create_table "books", force: :cascade do |t|
    t.string "cover", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["cover"], name: "index_books_on_cover"
  end
end
  1. Define an enum in the model with the array syntax
# apps/models/book.rb
class Book < ApplicationRecord
  enum cover: [:hard, :soft]
end
  1. Access Rails console and create an object
Book.create cover: "soft"
# => #<Book:0x00007fe231b30860
#     id: 1,
#     cover: nil,
#     created_at: Thu, 19 Aug 2021 02:23:56.794160000 UTC +00:00,
#     updated_at: Thu, 19 Aug 2021 02:23:56.794160000 UTC +00:00>
  1. Note that the cover column is nil even though Rails created the object.

Expected behavior

I think it would be neat if Rails could infer the enum value from its key for string columns. It already does this for integers (inferring by the array position), so I don’t think it’s a stretch for us to add this. Here’s what I’m thinking:

class Book < ApplicationRecord
  # for string columns, Rails would interpret it as
  # enum cover: { hard: "hard" , soft: "soft" }
  enum cover: [:hard, :soft]
end

Using the key as a value is probably the most common option for this case. I’ve seen a lot of apps using the following pattern to address this:

class Book < ApplicationRecord
  COVER_OPTIONS = [:hard, :soft, :other]
  enum cover: COVER_OPTIONS.map { |option| [option, option.to_s] }.to_h
end

This would help in those cases as well. I will tackle this if approved.

Actual behavior

Currently, it saves the object with the array index position as string, but it cannot load the value from the database, so the ActiveRecord object comes with the enum field as nil.

System configuration

Rails version: Rails 6.1.4

Ruby version: ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin20]

3 Likes

Yeah, we’ve wanted this for a while, so please submit a PR. In many ways, string columns should be the default, but we can handle that from the documentation and then leave the integer column as an optimization section or something like that.

1 Like

So, should I also update the enum documentation to string-first?

Yeah, we can start there.

I started working here Add support for array syntax on enums backed by string columns by MatheusRich · Pull Request #43085 · rails/rails · GitHub. Should we keep the discussion there?

There really is no beautiful way of doing this. Just set up an array of strings indexed by the enum.