ActiveAdmin和Formtastic:使用ENUM显示单选按钮和选择框的值

时间:2021-01-27 16:01:37

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:

我正在使用ActiveAdmin来管理一个大型数据库,我的一个模型(ItemType)有一个ENUM属性(ItemType.units),我可以使用Formtastic来呈现一个选择框和单选按钮,如下所示:

f.input :unit, :as => :radio, :collection => ItemType.units, include_blank: false

The ENUM field is defined in the model like so:

ENUM字段在模型中定义如下:

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:

下面是数据库记录的默认“单数视图”:

ActiveAdmin和Formtastic:使用ENUM显示单选按钮和选择框的值

And here's the default "edit view" for the same record. Notice how none of the values are selected:

这里是相同记录的默认“编辑视图”。请注意如何没有选择任何值:

ActiveAdmin和Formtastic:使用ENUM显示单选按钮和选择框的值

2 个解决方案

#1


4  

When you declare an enum and then access the mapping it returns a hash:

当您声明一个enum并访问映射时,它返回一个散列:

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").

当您使用它作为无线电输入的集合时,它将设置输入的值为0,1,2,等等。该值与ItemType#unit的返回值不匹配(因为它将返回enum的字符串表示,如“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.

Rails只能在列表中的一个值与属性值匹配时设置所选值。这种情况永远不会发生。

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).

这种对偶性(字符串与整数)将导致另一个痛苦点。实际上,您不能保存表单,因为您只能将enum的值设置为一个允许的值(字符串或整数表示)。

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.

由于表单解析被解析为字符串,ActiveAdmin将尝试将“1”分配给ItemType#unit,它将失败。

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:

尽管如果可以,你应该远离使用AR的枚举。几个原因:

  • it represent a meaningful string with a meaningless number (this means the data in the unit column will not make any sense without the application code)
  • 它表示一个具有无意义数字的有意义的字符串(这意味着在没有应用程序代码的情况下,单元列中的数据不会有任何意义)
  • it tightly couples the source of the application to the data (it would be hard to use the data by itself in an other application such as a db console)
  • 它将应用程序的源紧紧地连接到数据(在其他应用程序(如db控制台)中很难单独使用数据)
  • the order of enum values has to be maintained (or an explicit mapping has to be provided). Neither of them are developer friendly.
  • 必须维护enum值的顺序(或者必须提供显式映射)。它们都不适合开发人员。

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

#2


0  

I would use constants instead of enum:

我会使用常量而不是enum:

class ItemType < ActiveRecord::Base
  UNIT_VALUES = %w{Packages Pieces KG}
end

f.input :units, :as => :radio, :collection => ItemType::UNIT_VALUES, include_blank: false

#1


4  

When you declare an enum and then access the mapping it returns a hash:

当您声明一个enum并访问映射时,它返回一个散列:

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").

当您使用它作为无线电输入的集合时,它将设置输入的值为0,1,2,等等。该值与ItemType#unit的返回值不匹配(因为它将返回enum的字符串表示,如“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.

Rails只能在列表中的一个值与属性值匹配时设置所选值。这种情况永远不会发生。

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).

这种对偶性(字符串与整数)将导致另一个痛苦点。实际上,您不能保存表单,因为您只能将enum的值设置为一个允许的值(字符串或整数表示)。

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.

由于表单解析被解析为字符串,ActiveAdmin将尝试将“1”分配给ItemType#unit,它将失败。

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:

尽管如果可以,你应该远离使用AR的枚举。几个原因:

  • it represent a meaningful string with a meaningless number (this means the data in the unit column will not make any sense without the application code)
  • 它表示一个具有无意义数字的有意义的字符串(这意味着在没有应用程序代码的情况下,单元列中的数据不会有任何意义)
  • it tightly couples the source of the application to the data (it would be hard to use the data by itself in an other application such as a db console)
  • 它将应用程序的源紧紧地连接到数据(在其他应用程序(如db控制台)中很难单独使用数据)
  • the order of enum values has to be maintained (or an explicit mapping has to be provided). Neither of them are developer friendly.
  • 必须维护enum值的顺序(或者必须提供显式映射)。它们都不适合开发人员。

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

#2


0  

I would use constants instead of enum:

我会使用常量而不是enum:

class ItemType < ActiveRecord::Base
  UNIT_VALUES = %w{Packages Pieces KG}
end

f.input :units, :as => :radio, :collection => ItemType::UNIT_VALUES, include_blank: false