ankane/searchkick · error · ArgumentError
unsupported keywords: #{options.keys.map(&:inspect).join(",
Error message
unsupported keywords: #{options.keys.map(&:inspect).join(", ")} What it means
When a where value falls through to a term filter, Searchkick checks that value.as_json is a scalar. If it is still an Enumerable (a Set, a custom object serializing to an array/hash, etc.), Elasticsearch could not term-match it, so Searchkick raises TypeError with the Active Record-style message "can't cast Set". Arrays are auto-converted to a terms (in) query earlier, which is why sets and other enumerables hit this instead.
Source
Thrown at lib/searchkick/index.rb:239
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
true
else
async = options.delete(:async)
if async
if async.is_a?(Hash) && async[:wait]
Searchkick.warn "async option is deprecated - use mode: :async, wait: true instead"
options[:wait] = true unless options.key?(:wait)
else
Searchkick.warn "async option is deprecated - use mode: :async instead"
end
options[:mode] ||= :async
end
full_reindex(relation, **options)View on GitHub (pinned to 93e901a75b)
Solutions
- Convert collections explicitly: where: {status: set.to_a} - Arrays become a terms/in query automatically
- Or use the explicit operator: where: {status: {in: [...]}}
- For custom value objects, pass the primitive (e.g. money.amount) instead of the wrapper
Example fix
# before
Product.search('*', where: {status: Set.new(['new', 'used'])})
# => TypeError: can't cast Set
# after
Product.search('*', where: {status: Set.new(['new', 'used']).to_a}) Defensive patterns
Strategy: type-guard
Type guard
def term_value(v)
v = v.to_a if v.is_a?(Set)
raise TypeError, "can't cast #{v.class.name}" if v.as_json.is_a?(Enumerable)
v
end
Product.search('*', where: {status: term_value(input)}) Try / catch
begin
Product.search('*', where: filters)
rescue TypeError => e
raise unless e.message.start_with?("can't cast")
filters.each { |k, v| filters[k] = v.to_a if v.is_a?(Set) }
retry
end Prevention
- Normalize Set/Struct/custom objects to scalars or plain Arrays before building where hashes
- Convert Sets with .to_a so they become terms (in) queries
- Keep value objects out of filters - pass .amount/.id primitives
When it happens
Trigger: Product.search('*', where: {status: Set.new(['a', 'b'])}) - Sets are not Arrays so they skip the automatic {in: value} conversion; passing a Struct or any object whose as_json returns an Array/Hash as a term value.
Common situations: Using Set for tag/category filters; passing Money/Struct/value objects or ActionController::Parameters where a scalar is expected; params that arrive hash-like instead of as a plain value.
Related errors
- Requires non-OSS version of Elasticsearch
- Cannot reindex object
- Multiple clients found - set Searchkick.client_type = :elast
- No client found - install the `elasticsearch` or `opensearch
- The `elasticsearch` gem must be 8+
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/a51f8e19479c7907.
Report an issue: GitHub.