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

  1. Chain all conditions before reading: r = Product.search('milk').where(in_stock: true).page(2); then call r.results
  2. Start a fresh relation for the variant query instead of reusing the loaded object
  3. 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

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


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