Enums in ActiveRecord 4.1 and options_for_select


Update: This StackOverflow answer provides a better way to use a drop-down with enums. I could’ve sworn I tried that, but maybe not.

I discovered something interesting about the way ActiveRecord 4.1 enums work. If you’re trying to use them with options_for_select, you may need to do some little acrobatics with your enums.

Assume your model provides:

class Car < ActiveRecord::Base
  # body_style is an integer in the database
  enum body_style: [:coupe, :convertible, :van, :suv, :sedan]
end

And your update route for cars_controller does this:

class CarsController < ApplicationController
  before_action :set_car, only: [:show, :edit, :update, :destroy]

  # PATCH/PUT /cars/1
  # PATCH/PUT /cars/1.json
  def update
    respond_to do |format|
      if @car.update(car_params)
        format.html { redirect_to @car, notice: 'Car was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @car.errors, status: :unprocessable_entity }
      end
    end
  end
  private
    # Use callbacks to share common setup or constraints between actions.
    def set_car
      @car = Car.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def car_params
      params.require(:car).permit(:body_style)
    end
end

Then, using the enumeration in a form_for block in your view requires a deviation from what Car.body_styles will give you.

The value submitted by this form snippet won’t validate because update is expecting the “string” key, and not the underlying numerical value of the table.

<%= form_for(@car) do |f| %>
  <%= f.label :body_style %>
  <%= f.select :body_style, options_for_select(Car.body_styles) %>

Instead, a little bit of transformation needs to be done so that the description/value pair for the elements are both the text value of the enum.

<%= form_for(@car) do |f| %>
  <%= f.label :body_style %>
  <%= f.select :body_style, options_for_select(Car.body_styles.map {|k,v| [k,v]}) %>

Or, with the selected value included:

<%= form_for(@car) do |f| %>
  <%= f.label :body_style %>
  <%= f.select :body_style, options_for_select(Car.body_styles.map {|k,v| [k,v]}, @car.body_style) %>

%d bloggers like this: