Rails select_tag/dropdown with enums
Asked Answered
M

4

24

I'm got a model Lead which has a field Status which consists of multiple values via an enum.

enum status: { open: 0, closed: 1, qualified: 2, rejected: 3 }

I'm trying to create a select-field (in a table) where the new status will be saved via ajax. Can someone help me to create the select_tag, online I can find enum's which work but because I have both ID's and values it's a bit more complicated.

At this moment I have something like this, though it doesn't work:

  <tbody>
    <% @leads.each do |lead| %>
    <tr class="<%=cycle('odd', 'even') %> location_row" id="lead_row" data-id="<%= lead.id%>">
      <td><%= lead.id %></td>
      <td><%= lead.fullname %></td>
      <td><%= lead.email %></td>
      <td><%= lead.phone %></td>
      <td><%= select_tag :Status, Lead.statuses.keys.to_a %></td> #trying this, without luck
      <td><%= select_tag :Status, Lead.statuses.keys.to_a.map { |w, v| [w.titleize, v] }%></td> #2nd try, without luck
      <td><%= link_to (fa_icon "pencil-square-o "), edit_lead_path({:id => lead.id, :first_last_name => lead.first_last_name}), :title => 'Edit Lead', :class => "action-button" %></td>
    </tr>
    <% end %>
  </tbody>

Thanks, T

Machicolation answered 7/6, 2016 at 8:25 Comment(0)
M
42

I found the answer myself by trial-and-error.

<%= select_tag :status, options_for_select(Lead.statuses.map {|k, v| [k.humanize.capitalize, v]}) %>
Machicolation answered 7/6, 2016 at 8:37 Comment(0)
Z
16

You can use following code for select status enum as options:
<%= select_tag :status, options_for_select(Lead.statuses) %>

Zephaniah answered 7/6, 2016 at 8:54 Comment(2)
Works well in Rails 4.2.8 if you don't need the capitalized text.Muenster
This also works on Rails 7 (lower_case)Mertz
W
9

Both answers are good but they'll always show the default list of enum options.

If you want your dropdown to show an already existing value (e.g. in an edit form for an existing record), add your existing value as the second parameter to options_for_select:

<%= f.select :access_level, options_for_select(Lead.statuses.map { |key, value| 
    [key.capitalize, value]
  }, @lead.status) %>

Also in many cases key.humanize is a better choice than key.capitalize, because it turns underscores into spaces.

Wyatt answered 18/10, 2020 at 18:29 Comment(1)
I had an issue with this where the existing option was not being selected because options_for_select wanted a match of the value. I used @lead.status_before_type_cast and it worked.Freytag
P
0

For those looking for an option that also translates, first add the translations the same way rails internationlization suggests:

en:
  activerecord:
    attributes:
      lead/status:
        open: Open
        closed: Closed
        qualified: Qualified
        rejected: Rejected

Then you have some options, like creating a helper or a class method. I personally prefer having a class method, since there is already a human_attribute_name so a human_enum method feels like a good fit.

1. Using a helper:

# app/helpers/application_helper.rb
module ApplicationHelper
  def options_for_enum(model_class, enum)
    enum_hash = model_class.send(enum.to_s.pluralize)

    enum_hash.map do |key, value|
      [model_class.human_attribute_name("#{enum}.#{key}"), value]
    end
  end
end

Use the helper in your view

<%= form.select :status, options_for_enum(Lead, :status) %>

2. Adding a class method directly in ApplicationRecord

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

  def self.human_enum(enum, options = {})
      enum_hash = send(enum.to_s.pluralize)
      enum_hash.transform_keys { |key| human_attribute_name("#{enum}.#{key}", options) }
  end
end

Call the human_enum method from your view

<%= form.select :status, Lead.human_enum(:status), { include_blank: false }, { class: "form-select" } %>

2.1 Adding a class method using concerns

Extra step to leave the base ApplicationRecord a bit cleaner

# app/models/concerns/human_enum.rb
module HumanEnum
  extend ActiveSupport::Concern

  class_methods do
    def human_enum(enum, options = {})
      enum_hash = send(enum.to_s.pluralize)
      enum_hash.transform_keys { |key| human_attribute_name("#{enum}.#{key}", options) }
    end
  end
end
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

  include HumanEnum
end
Pod answered 28/2 at 19:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.