I'm using ActiveAdmin to manage a large database and one of my models (ItemType) has an ENUM attribute (ItemType.units) and I am able to use Formtastic to render a select box and radio buttons like so:
f.input :unit, :as => :radio, :collection => ItemType.units, include_blank: false
The ENUM field is defined in the model like so:
class ItemType < ActiveRecord::Base
enum unit: [ :Packages, :Pieces, :KG ]
end
This works correctly when creating a new resource, but the value is not retrieved when using the form to edit the same resource.
Here's the default "singular view" for the database record:
And here's the default "edit view" for the same record. Notice how none of the values are selected:
When you declare an enum and then access the mapping it returns a hash:
ItemType.units #=> { "Packages" => 0, "Pieces" => 1, "KG" => 2 }
When you are using it as a collection for your radio inputs it will set the value of the inputs to 0, 1, 2, etc. That value does not match the return value of ItemType#unit
(as it will return the string representation of the enum such as "KG"
).
Rails can only set the selected value when one of the values in the list matches the attribute's value. This will never happen in this case.
This duality (string vs integer) will lead to an other pain point. You cannot actually save the form because you can only set the enum's value to one of the allowed values (string or integer representations).
it = ItemType.new
it.unit = "KG" # this works
it.unit = :KG # this works as well
it.unit = 1 # this works but WTF?!
it.unit = "1" # this will raise an ArgumentError
Since the form params are parsed into a string ActiveAdmin will try to assign "1"
to ItemType#unit
and it will fail.
The solution is actually pretty simple. Use only the keys from the mapping:
f.input :unit, :as => :radio, :collection => ItemType.units.keys
Although if you can you should stay away from using AR's enums. A few reasons why:
It is a better alternative to use a predefined string array and validate whether the value is in the list of predefined values. Something along the lines:
class ItemType
UNITS = %w[kg packages pieces]
validates :unit, inclusion: { in: UNITS }
end