railsadminteam/rails_admin · error · ArgumentError

Unsupported sort value: #{options[:sort]}

Error message

Unsupported sort value: #{options[:sort]}

What it means

RailsAdmin's ActiveRecord adapter builds the ORDER BY clause in sort_scope (lib/rails_admin/adapters/active_record.rb:160). options[:sort] must be a String/Symbol column name, an Array of columns, or a one-level Hash of {table_name => column}; any other class raises ArgumentError before SQL is generated. The value normally comes from MainController#get_sort_hash (app/controllers/rails_admin/main_controller.rb:66), which uses the matched field's sort_column (a String) or falls back to the model's list.sort_by configuration.

Source

Thrown at lib/rails_admin/adapters/active_record.rb:171

        if primary_key.is_a? Array
          options[:bulk_ids].map { |id| primary_key_scope(scope, id) }.reduce(&:or)
        else
          scope.where(primary_key => options[:bulk_ids])
        end
      end

      def sort_scope(scope, options)
        direction = options[:sort_reverse] ? :asc : :desc
        case options[:sort]
        when String, Symbol
          scope.reorder("#{options[:sort]} #{direction}")
        when Array
          scope.reorder(options[:sort].zip(Array.new(options[:sort].size) { direction }).to_h)
        when Hash
          scope.reorder(options[:sort].map { |table_name, column| "#{table_name}.#{column}" }.
            zip(Array.new(options[:sort].size) { direction }).to_h)
        else
          raise ArgumentError.new("Unsupported sort value: #{options[:sort]}")
        end
      end

      class WhereBuilder
        def initialize(scope)
          @statements = []
          @values = []
          @tables = []
          @scope = scope
        end

        def add(field, value, operator)
          field.searchable_columns.flatten.each do |column_infos|
            statement, value1, value2 = StatementBuilder.new(column_infos[:column], column_infos[:type], value, operator, @scope.connection.adapter_name).to_statement
            @statements << statement if statement.present?
            @values << value1 unless value1.nil?
            @values << value2 unless value2.nil?
            table, column = column_infos[:column].split('.')

View on GitHub (pinned to 0690a5e62f)

Solutions

  1. If the value comes from config, make list.sort_by a plain column name: `list { sort_by :created_at }` — or a String/Symbol like 'players.name', an Array [:last_name, :first_name], or a flat Hash {players: :name} for association sorting.
  2. If you call abstract_model.all / the adapter yourself, pass only String, Symbol, Array of column names, or a one-level {table => column} Hash as the :sort value.
  3. For computed sorting (Arel/SQL expressions), pass the raw SQL string form that the String branch accepts, or wrap the call and apply reorder() yourself on the returned scope.
  4. If the sort value can be user-supplied, validate its class before it reaches the adapter (see defense) instead of letting ArgumentError surface as a 500.

Example fix

# before (raises ArgumentError: Unsupported sort value)
RailsAdmin.config do |config|
  config.model Player do
    list { sort_by 123 }
  end
end
abstract_model.all(sort: Player.arel_table[:name].asc)

# after
RailsAdmin.config do |config|
  config.model Player do
    list { sort_by :created_at }   # String, Symbol, Array, or {table: :column} Hash
  end
end
abstract_model.all(sort: 'players.name')
Defensive patterns

Strategy: type-guard

Validate before calling

# Before calling the adapter / building list options
sort = options[:sort]
unless sort.nil? || [String, Symbol, Array, Hash].any? { |k| sort.is_a?(k) }
  raise ArgumentError, "options[:sort] must be String/Symbol/Array/Hash, got #{sort.class}"
end
# also reject nested hashes: only {table => column} one level deep is valid
options[:sort] = nil if sort.is_a?(Hash) && sort.values.any? { |v| v.is_a?(Hash) }

Type guard

# Ruby type guard mirroring sort_scope's case statement (active_record.rb:162)
valid_sort_value = lambda do |v|
  v.is_a?(String) || v.is_a?(Symbol) ||
    (v.is_a?(Array) && v.all? { |c| c.is_a?(String) || c.is_a?(Symbol) }) ||
    (v.is_a?(Hash) && v.values.all? { |c| c.is_a?(String) || c.is_a?(Symbol) })
end
valid_sort_value.call(options[:sort]) # => true/false

Try / catch

begin
  scope = abstract_model.all(sort: requested_sort, sort_reverse: params[:sort_reverse])
rescue ArgumentError => e
  Rails.logger.warn("RailsAdmin sort rejected (#{e.message}); falling back to unsorted scope")
  scope = abstract_model.all # retry without sort rather than 500ing the index page
end

Prevention

When it happens

Trigger: Calling AbstractModel#all (or list_entries) with options[:sort] that is not String/Symbol/Array/Hash — e.g. abstract_model.all(sort: 123) or sort: User.arel_table[:name].asc (an Arel node). Or configuring `list { sort_by <non-column-object> }`, since get_sort_hash falls back to list.sort_by verbatim when params[:sort] does not match a field name. A Hash deeper than one level (nested hashes as values) also lands in the unsupported branch behavior.

Common situations: Custom code or specs that call the adapter/all API directly with an old or hand-built options hash; a model config where sort_by is assigned something other than a column name (Proc result, Integer, Arel node); upgrading an app whose sort_by was tolerated by an older adapter version; passing a nested sortable Hash instead of the flat {table => column} form.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of railsadminteam/rails_admin@0690a5e62f (2026-08-21). Data as JSON: /api/errors/43a32d15b1aabe22. Report an issue: GitHub.