array_enum
array_enum copied to clipboard
Feature Request: Support string array enum values
Currently array_enum assumes you have integer values stored in the db (and I recognize that the README explains this clearly). The query it generates casts values as integers:
where("#{table_name}.#{attr_name} #{comparison_operator} ARRAY[:db_values]::integer[]", db_values: db_values)
The generated scopes cause errors for any implementations where you have an array_enum defined like
array_enum roles: ["foo", "bar", "baz"].index_by(&:itself) # this is a string array column
It seems pretty trivial to just assume integer or varchar based on the values:
def array_enum(definitions)
definitions.each do |attr_name, mapping|
attr_symbol = attr_name.to_sym
mapping_hash = ActiveSupport::HashWithIndifferentAccess.new(mapping)
define_singleton_method(attr_name.to_s.pluralize) do
mapping_hash
end
{
"with_#{attr_name}" => '@>',
"only_with_#{attr_name}" => '=',
"with_any_of_#{attr_name}" => '&&'
}.each do |method_name, comparison_operator|
define_singleton_method(method_name.to_sym) do |values|
cast_as = "integer"
db_values = Array(values).map do |value|
unless value.is_a?(Integer)
cast_as = "varchar"
end
mapping_hash[value] || raise(ArgumentError, format(MISSING_VALUE_MESSAGE, value: value, attr: attr_name))
end
where("#{table_name}.#{attr_name} #{comparison_operator} ARRAY[:db_values]::#{cast_as}[]", db_values: db_values)
end
end
define_method(attr_symbol) do
Array(self[attr_symbol]).map { |value| mapping_hash.key(value) }
end
define_method("#{attr_name}=".to_sym) do |values|
self[attr_symbol] = Array(values).map do |value|
mapping_hash[value] || raise(ArgumentError, format(MISSING_VALUE_MESSAGE, value: value, attr: attr_name))
end.uniq
end
end
end
Side Note: Rails has changed the argument structure for enums. This would be a breaking change for this gem but consider updating the API to look like:
array_enum :roles, ["foo", "bar", "baz"].index_by(&:itself)
Happy to open a pr(s) for this!
Thanks for the great gem!