ActiveAdmin and Formtastic: Displaying value for radio buttons and field selection using ENUM
I am using ActiveAdmin to manage a large database and one of my models (ItemType) has an ENUM attribute (ItemType.units) and I can use Formtastic to render a select box and radio buttons like:
f.input :unit, :as => :radio, :collection => ItemType.units, include_blank: false
The ENUM field is defined in the model as follows:
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 a form to edit the same resource.
This uses the default "special view" for the database record:
And here, by default, a "preview" is displayed for the same entry. Note that none of the values ββare selected:
source to share
When you declare an enum and then access the display, it returns a hash:
ItemType.units #=> { "Packages" => 0, "Pieces" => 1, "KG" => 2 }
When you use it as a collection for your radio inputs, it sets the inputs to 0, 1, 2, etc. This value does not match the return value ItemType#unit
(since it will return a string representation of an enumeration such as "KG"
).
Rails can only set the selected value when one of the values ββin the list matches the value of the attribute. This will never happen in this case.
This duality (string vs integer) will lead to another point of pain. You cannot actually save the form because you can only set the enum value to one of the allowed values ββ(string or integer representation).
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 parameters 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 keys from display:
f.input :unit, :as => :radio, :collection => ItemType.units.keys
Although, if you can, you should stay away from using AR enums. A few reasons:
- it is a meaningful string with a meaningless number (this means that the data in the one column won't make any sense without application code)
- it tightly connects the application source to the data (it would be difficult to use the data separately in another application like the db console).
- the order of the enumeration values ββmust be maintained (or an explicit mapping must be provided). None of them support developers.
Your best bet is to use a predefined array of strings and check if the value is in the list of predefined values. Something like lines:
class ItemType
UNITS = %w[kg packages pieces]
validates :unit, inclusion: { in: UNITS }
end
source to share