array_enum icon indicating copy to clipboard operation
array_enum copied to clipboard

Feature Request: Support string array enum values

Open htcarr3 opened this issue 6 months ago • 0 comments

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!

htcarr3 avatar Jun 25 '25 13:06 htcarr3