Salvando enum de select no Rails 4.1

108

Estou usando os enums no Rails 4.1 para controlar as cores do vinho.

Wine.rb

class Wine < ActiveRecord::Base
    enum color: [:red, :white, :sparkling]
end

Na minha opinião, eu gero um select para que o usuário possa selecionar um vinho com uma determinada cor

f.input :color, :as => :select, :collection => Wine.colors

Isso gera o seguinte HTML:

<select id="wine_color" name="wine[color]">
  <option value=""></option>
  <option value="0">red</option>
  <option value="1">white</option>
  <option value="2">sparkling</option>
</select>

No entanto, ao enviar o formulário, recebo um erro de argumento informando '1' is not a valid color. Eu percebo que isso é porque colordeve ser igual 1e não "1".

Existe uma maneira de forçar o Rails a interpretar a cor como um inteiro ao invés de uma string?

Brian Weinreich
fonte

Respostas:

203

Tudo bem, então, aparentemente, você não deve enviar o valor inteiro do enum para ser salvo. Você deve enviar o valor de texto do enum.

Eu mudei a entrada para ser a seguinte:

f.input :color, :as => :select, :collection => Wine.colors.keys.to_a

Que gerou o seguinte HTML:

<select id="wine_color" name="wine[color]">
  <option value=""></option>
  <option value="red">red</option>
  <option value="white">white</option>
  <option value="sparkling">sparkling</option>
</select>

Os valores foram de "0" a "vermelho" e agora estamos prontos.


Se você estiver usando um antigo text_field normal, ele é:

f.select :color, Wine.colors.keys.to_a


Se você quiser ter atributos legíveis por humanos, também pode fazer:

f.select :color, Wine.colors.keys.map { |w| [w.humanize, w] }

Brian Weinreich
fonte
8
FWIW, eu precisava fazer isso f.select :color, Wine.colors.to_a.map { |w| [w[0].humanize, w[0]] }porque wrepresentava uma matriz. Não sei por que, mas talvez isso ajude alguém.
jakealbaugh
usar titleizepode ser uma ideia melhor se você tiver duas ou mais palavras
Anwar
8
o keysmétodo retorna uma matriz, portanto, o to_aé redundante. Alternativamente, o método auxiliar de seleção usa uma matriz 2D para opções para que você possa apenas usar to_a.
xander-miller
E se uma string inválida for enviada, como "ERROR_JUNK_Submission". Obviamente, não há valor enum como esse e uma exceção é lançada. Onde o pegaríamos?
FlyingV
O modelo Wine tem um enum chamado color, enum color: [:red, :white, :sparkling]então a frase correta éf.input :color, :as => :select, :collection => Wine.color.keys.to_a
hguzman
32

Não há necessidade de converter o hash enum em array com to_a. Isso é suficiente:

f.select :color, Wine.colors.map { |key, value| [key.humanize, key] }
Companheiro estranho
fonte
10

Acabei de criar um EnumHelper que pensei em compartilhar para ajudar as pessoas que precisam de rótulos e locais de enum mais personalizados para suas seleções de enum.

module EnumHelper

  def options_for_enum(object, enum)
    options = enums_to_translated_options_array(object.class.name, enum.to_s)
    options_for_select(options, object.send(enum))
  end

  def enums_to_translated_options_array(klass, enum)
    klass.classify.safe_constantize.send(enum.pluralize).map {
        |key, value| [I18n.t("activerecord.enums.#{klass.underscore}.#{enum}.#{key}"), key]
    }
  end

end

Em sua localidade:

 en:
   activerecord:
     enums:
      wine:
        color:
          red:   "Red Wine"
          white:  "White Wine"

Em suas opiniões:

 <%= f.select(:color, options_for_enum(@wine, :color)) %>
Andrew Cetinic
fonte
1
A edição torna as chaves de tradução mais legíveis ao encontrar enums pertencentes a MultipleWordClassName sob o bastante mais típico multiple_word_class_name em vez de multiplewordclassname
Tetsujin no Oni
Que tal adicionar um padrão para um substituto elegante: [I18n.t("activerecord.enums.#{klass.underscore}.#{enum}.#{key}", default: key.humanize), key] Além disso, remova o último humanize, pois pode distorcer a capitalização se houver várias palavras
Peter P.
9

A solução aceita não funcionou para mim para a leitura humana , mas fui capaz de fazê-la funcionar assim:

<%= f.select(:color, Wine.colors.keys.map {|key| [key.humanize, key]}) %>

Este foi o mais limpo, mas eu realmente precisava humanizar minhas chaves:

<%= f.select(:color, Wine.colors.keys) %>
Tom Rossi
fonte
1
o rails 5 suporta a maneira mais limpa agora
V-SHY
@V-SHY, o que é esse jeito, pode me explicar?
ismailarilik
2
@ismailarilik, forneça as chaves diretamente como opção de seleção, <% = f.select (: color, Wine.colors.keys)%>
V-SHY
6

Se você usar enum no Rails 4, basta chamar Model.enums:

f.select :color, Wine.colors.keys

Para criar HTML:

<select name="f[color]" id="f_color">
    <option value="red">red</option>
    <option value="white">white</option>
    <option value="sparkling"> sparkling </option>
</select>

Ou adicione o método no controlador:

def update_or_create
    change_enum_to_i
    ....
end

def change_enum_to_i
    params[:f]["color"] = params[:f]["color"].to_i
end
Ogelacinyc
fonte
4

Aqui está o que funcionou para mim, Rails 4+:

class Contract < ApplicationRecord

enum status: { active:  "active", 
               ended: "active", 
               on_hold: "on_hold", 
               terminated:  "terminated", 
               under_review:  "under_review" , 
               unknown: "unknown" 
              }


end

em my _form.html.erb, eu tenho isso:

  <div class="field">
    <%= form.select :status, Contract.statuses.keys, {}%>
  </div>

teste do console após adicionar um registro:

2.3.0 :001 > Contract.last.status
  Contract Load (0.2ms)  SELECT  "contracts".* FROM "contracts" ORDER BY "contracts"."id" DESC LIMIT ?  [["LIMIT", 1]]
 => "active"
zee
fonte
3

Se você precisar lidar com o i18n com base nas chaves enum, poderá usar:

<%= f.select :color, Wine.colors.keys.map {|key| [t("wine.#{key}"), key]} %>

e nas traduções você pode definir as cores:

wine:
 red: Red
 white: White
Paulo fidalgo
fonte
1

Esta é minha solução (minhas funções têm sublinhados como "sales_rep") e, por algum motivo, foi assim que eu precisei fazer com que uma opção em branco funcionasse (com o simpleform?):

Em ApplicationHelper:

def enum_collection_for_select(attribute, include_blank = true)
  x = attribute.map { |r| [r[0].titleize, r[0]] }
  x.insert(0,['', '']) if include_blank == true
  x
end

Então, na minha forma:

<%= f.input :role, collection: enum_collection_for_select(User.roles), selected: @user.role %>
Greg Blass
fonte