ruby-on-railsformsdrop-down-menuenums

Enums in Ruby on Rails Form Select Mapping Values


I have an enumeration in my model as follows:

 enum construction_type: {
    brick_block: "Brick/Block",
    concrete_slab: "Concrete/Slab",
    wood_steel: "Light Framed Wood/Steel",
    timber_steel: "Heavy Framed Timber/Steel"
  }

In a form, I use this code to grab the values of the enum to put into a dropdown:

  <%= form.label(:construction_type, class: "form-label") %>
  <% options = options_for_select(Site.construction_types.map {|key, value| [value, Site.construction_types.key(value)]}, form.object.construction_type) %>
  <%= form.select(:construction_type, options, include_blank: true) %>

While the statement in options_for_select seems like overkill when Site.construction_types.values yields the same options, the field only remains populated after an invalid submit on the form when using the mapped method.

One solution I've found is to hardcode the strings into the form as follows:

  <%= form.label(:construction_type, class: "form-label") %>
  <%= form.select(:construction_type, ["Brick/Block", "Concrete/Slab", "Light Framed Wood/Steel", "Heavy Framed Timber/Steel"], include_blank: true) %>

However, I would like to avoid this solution as I have a second form used to edit the information initialized in this one where I would have to duplicate the code. An enumeration in the model seems like the best way to keep track of these values.

My database populates as I would like with the values from the enum, but on a page where I am trying to display the information from the form, the keys appear instead.

<li> <strong> <%= t(".construction_type") %> </strong> <%=@site.construction_type if @site.construction_type %> </li>

Using the enumerated version, the code above yields the following: Construction Type: brick_block

As opposed to what I want: Construction Type: Brick/Block

Is there a way to fix this using the enumeration method?


Solution

  • An enumeration in the model seems like the best way to keep track of these values.

    Hell no. ActiveRecord::Enum is designed to connect a integer or any other type thats efficient to store and index to a developer readable label.

    When you define your enum as:

    enum construction_type: {
        brick_block: "Brick/Block",
        concrete_slab: "Concrete/Slab",
        wood_steel: "Light Framed Wood/Steel",
        timber_steel: "Heavy Framed Timber/Steel"
      }
    

    You're going to be storing "Heavy Framed Timber/Steel" as the value in the database which is a downright bad idea as you're asking for denormalization issues if you ever need to change the human friendly labels. The enum mapping should not be expected to change.

    If you really want to use an Enum then use the I18n module to provide the human readible versions:

    # the name is just an assumption
    class Building < ApplicationRecord
      enum construction_type: {
        brick_block: 0,
        concrete_slab: 1,
        wood_steel: 2,
        timber_steel: 3
      }
    end
    
    module BuildingsHelper
      def construction_type_options
         Building.construction_types.keys do |key|
           [key, t("activerecord.models.buildings.construction_types.#{ key }")]
         end
      end
    end
    

    But a less hacky alternative is to use a seperate table/model:

    class Building
      belongs_to :construction_type
    end
    
    class ConstructionType
      has_many :buildings
    end
    
    <%= form_with(model: @buildling) do |form| %>
      <%= form.collection_select :construction_type_id, 
        ConstructionType.all,
        :id,
        :description
      %>
    <% end %>