ankane/searchkick · error · Searchkick::Error

Cannot reindex object

Error message

Cannot reindex object

What it means

Inside where hashes, field: {op: value} supports a fixed operator set: :in, :exists, :not, and the range comparisons :gt, :gte, :lt, :lte. Any other key raises ArgumentError 'Unknown where operator: ...' with the offending operator inspected, so typos and SQL-style names fail fast at query-build time.

Source

Thrown at lib/searchkick/index.rb:225

      ReindexQueue.new(name)
    end

    # reindex

    # note: this is designed to be used internally
    # so it does not check object matches index class
    def reindex(object, method_name: nil, ignore_missing: nil, full: false, **options)
      if @options[:job_options]
        options[:job_options] = (@options[:job_options] || {}).merge(options[:job_options] || {})
      end

      if object.is_a?(Array)
        # note: purposefully skip full
        return reindex_records(object, method_name: method_name, ignore_missing: ignore_missing, **options)
      end

      if !object.respond_to?(:searchkick_klass)
        raise Error, "Cannot reindex object"
      end

      scoped = Searchkick.relation?(object)
      # call searchkick_klass for inheritance
      relation = scoped ? object.all : Searchkick.scope(object.searchkick_klass).all

      refresh = options.fetch(:refresh, !scoped)
      options.delete(:refresh)

      if method_name || (scoped && !full)
        mode = options.delete(:mode) || :inline
        scope = options.delete(:scope)
        job_options = options.delete(:job_options)
        raise ArgumentError, "unsupported keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?

        # import only
        import_scope(relation, method_name: method_name, mode: mode, scope: scope, ignore_missing: ignore_missing, job_options: job_options)
        self.refresh if refresh

View on GitHub (pinned to 93e901a75b)

Solutions

  1. Use the supported operators: {gt:}, {gte:}, {lt:}, {lte:}, {in: [...]}, {exists: true/false}, {not: ...}
  2. Negation is :not - where: {status: {not: 'sold'}} - not :ne/:not_eq
  3. Validate operator keys against a whitelist when filters are built from user input

Example fix

# before
Product.search('*', where: {price: {gteq: 10, ne: nil}})
# => ArgumentError: Unknown where operator: :gteq

# after
Product.search('*', where: {price: {gte: 10}, status: {not: 'sold'}})
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED_OPS = %i[in exists not gt gte lt lte].freeze

def sanitize_where(field_ops)
  field_ops.slice(*ALLOWED_OPS)
end

Product.search('*', where: {price: sanitize_where(params_ops)})

Try / catch

begin
  Product.search('*', where: where_filters)
rescue ArgumentError => e
  raise unless e.message.start_with?('Unknown where operator')
  where_filters.transform_keys! { |k| ALIAS[k] || k } # e.g. eq -> gte-less designs; log unknown
  retry
end

Prevention

When it happens

Trigger: Product.search('*', where: {price: {eq: 5}}), where: {created_at: {after: date}} or where: {status: {ne: 'sold'}}. Valid names are :gt/:gte/:lt/:lte, :in, :exists, :not (plus :_or/:_and/:_not/:_script at the top level).

Common situations: Translating SQL or ActiveRecord conditions (eq/ne/like/after/between) to Searchkick; building operator hashes dynamically from user input; typos like :gteq or :greater_than.

Related errors


AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21). Data as JSON: /api/errors/249398c54b4e9452. Report an issue: GitHub.