ankane/searchkick · error · Searchkick::Error
search must be called on model, not relation
Error message
search must be called on model, not relation
What it means
`searchkick_search` is defined on the model's eigenclass and first checks `Searchkick.relation?(self)`, which is true when `klass.current_scope` is non-nil — i.e. when the method is invoked while an ActiveRecord scope is active (Relation#method_missing delegates `search` to the class inside `scoping { }`). Because searchkick deliberately ignores scopes when querying (it filters only via `where:`), calling search under an active scope would silently drop the scope's conditions, so it raises `Searchkick::Error` instead.
Source
Thrown at lib/searchkick/model.rb:71
data
end unless base.method_defined?(:search_data)
def should_index?
true
end unless base.method_defined?(:should_index?)
end
class_eval do
cattr_reader :searchkick_options, :searchkick_klass, instance_reader: false
class_variable_set :@@searchkick_options, options.dup
class_variable_set :@@searchkick_klass, self
class_variable_set :@@searchkick_index_cache, Searchkick::IndexCache.new
class << self
def searchkick_search(term = "*", **options, &block)
if Searchkick.relation?(self)
raise Searchkick::Error, "search must be called on model, not relation"
end
Searchkick.search(term, model: self, **options, &block)
end
alias_method Searchkick.search_method_name, :searchkick_search if Searchkick.search_method_name
def searchkick_index(name: nil)
index_name = name || searchkick_klass.searchkick_index_name
index_name = index_name.call if index_name.respond_to?(:call)
index_cache = class_variable_get(:@@searchkick_index_cache)
index_cache.fetch(index_name) { Searchkick::Index.new(index_name, searchkick_options) }
end
alias_method :search_index, :searchkick_index unless method_defined?(:search_index)
def searchkick_reindex(method_name = nil, **options)
searchkick_index.reindex(self, method_name: method_name, **options)
end
alias_method :reindex, :searchkick_reindex unless method_defined?(:reindex)View on GitHub (pinned to 93e901a75b)
Solutions
- Call search on the model class and move scope conditions into the query: `Product.search("milk", where: {active: true})`.
- For chained scopes, extract the scope's conditions or use `unscoped`: `Product.unscoped.search("milk")` (note: this drops the scope on purpose).
- If the model has a `default_scope` you never want applied to search, add `unscope: true` to `searchkick` options so searchkick unscopes internally.
- For Mongoid criteria, call `.search` on the document class, not a `Criteria` object.
Example fix
# before
Product.where(active: true).search("milk")
# => Searchkick::Error: search must be called on model, not relation
# after
Product.search("milk", where: {active: true}) Defensive patterns
Strategy: validation
Validate before calling
def safe_search(klass, term = "*", **options)
raise Searchkick::Error, "call search on #{klass}, not a relation/scope" if Searchkick.relation?(klass)
klass.search(term, **options)
end
# usage: safe_search(Product, "milk", where: {active: true}) Type guard
def searchable_receiver?(klass) !Searchkick.relation?(klass) # true when no scope is active on klass end
Prevention
- Wrap search calls in one helper/query object so the model-vs-relation rule is enforced in one place.
- Translate scopes into `where:` conditions; searchkick never applies scopes to queries by design.
- Add `unscope: true` to models with a `default_scope` you don't want affecting searches.
When it happens
Trigger: `Product.where(active: true).search("milk")`, `Product.published.search("milk")`, calling `search` inside `Model.scoping { }` / another scope's body, or on a model with a `default_scope` and no `unscope: true` option. Also Mongoid criteria scopes via the Mongoid branch of `relation?`.
Common situations: New teams chaining searchkick onto relation chains like any other scope (`Model.where(...).limit(5).search`); models with `default_scope` where every search raises; refactoring from `Model.search` to chained query builders; Mongoid apps using criteria.
Related errors
- Can't pass both language and stemmer
- 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/97643175f7da9e27.
Report an issue: GitHub.