ankane/searchkick · error · ArgumentError
Can't pass both language and stemmer
Error message
Can't pass both language and stemmer
What it means
search calls return a lazy Searchkick::Relation; once results are materialized (results, each, to_a, hits) the relation is in a loaded state. Later chainable calls (where, where_not, limit, order, page, per_page, aggs, ...) all pass through check_loaded, which raises Error 'Relation loaded' instead of silently building a divergent query from the executed one.
Source
Thrown at lib/searchkick/index_options.rb:154
},
searchkick_stemmer: {
# use stemmer if language is lowercase, snowball otherwise
type: language == language.to_s.downcase ? "stemmer" : "snowball",
language: language || "English"
}
},
char_filter: {
# https://www.elastic.co/guide/en/elasticsearch/guide/current/custom-analyzers.html
# &_to_and
ampersand: {
type: "mapping",
mappings: ["&=> and "]
}
}
}
}
raise ArgumentError, "Can't pass both language and stemmer" if options[:stemmer] && language
update_language(settings, language)
update_stemming(settings)
if Searchkick.env == "test"
settings[:number_of_shards] = 1
settings[:number_of_replicas] = 0
end
if options[:similarity]
settings[:similarity] = {default: {type: options[:similarity]}}
end
settings[:index] = {
max_ngram_diff: 49,
max_shingle_diff: 4
}
if options[:knn]View on GitHub (pinned to 93e901a75b)
Solutions
- Chain all conditions before reading: r = Product.search('milk').where(in_stock: true).page(2); then call r.results
- Start a fresh relation for the variant query instead of reusing the loaded object
- Check r.loaded? before attempting to chain more options
Example fix
# before
r = Product.search('milk')
r.results # loads the relation
r.where(in_stock: true) # => Searchkick::Error: Relation loaded
# after
r = Product.search('milk').where(in_stock: true)
r.results Defensive patterns
Strategy: validation
Validate before calling
def refine(relation)
raise Searchkick::Error, 'Relation loaded' if relation.loaded?
relation
end
r = refine(Product.search('milk')).where(in_stock: true)
r.results Type guard
def chainable?(relation) !relation.loaded? end
Prevention
- Build the full chain (where/order/page) before touching results or hits
- Never memoize a relation you plan to mutate later - memoize its results or build a new relation
- Treat loaded? relations as immutable; copy options into a fresh search for variants
When it happens
Trigger: r = Product.search('milk'); r.results; r.where(in_stock: true) - or any hits/results/map first, then page(2)/order(:price) chained on the same relation object.
Common situations: Memoizing a relation in a controller, rendering results, then trying to refine or paginate it later; view helpers that both iterate and mutate; copying ActiveRecord habits where chaining after load builds a new relation.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- search must be called on model, not relation
- Multiple clients found - set Searchkick.client_type = :elast
- No client found - install the `elasticsearch` or `opensearch
- The `elasticsearch` gem must be 8+
- Use Searchkick.search to search multiple models
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/f5456276dae77359.
Report an issue: GitHub.