{"record":{"id":"c2b4ec76ccef9629","repo":"ankane/searchkick","slug":"use-searchkick-search-to-search-multiple-models","errorCode":null,"errorMessage":"Use Searchkick.search to search multiple models","messagePattern":"Use Searchkick\\.search to search multiple models","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/searchkick.rb","lineNumber":177,"sourceCode":"\n    # convert index_name into models if possible\n    # this should allow for easier upgrade\n    if options[:index_name] && !options[:models] && Array(options[:index_name]).all? { |v| v.respond_to?(:searchkick_index) }\n      options[:models] = options.delete(:index_name)\n    end\n\n    # make Searchkick.search(models: [Product]) and Product.search equivalent\n    unless klass\n      models = Array(options[:models])\n      if models.size == 1\n        klass = models.first\n        options.delete(:models)\n      end\n    end\n\n    if klass\n      if (options[:models] && Array(options[:models]) != [klass]) || Array(options[:index_name]).any? { |v| v.respond_to?(:searchkick_index) && v != klass }\n        raise ArgumentError, \"Use Searchkick.search to search multiple models\"\n      end\n    end\n\n    options = options.merge(block: block) if block\n    Relation.new(klass, term, **options)\n  end\n\n  def self.multi_search(queries, opaque_id: nil)\n    return if queries.empty?\n\n    queries = queries.map { |q| q.send(:query) }\n    event = {\n      name: \"Multi Search\",\n      body: queries.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| \"#{v}\\n\" }.join\n    }\n    ActiveSupport::Notifications.instrument(\"multi_search.searchkick\", event) do\n      MultiSearch.new(queries, opaque_id: opaque_id).perform\n    end","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/ankane/searchkick/blob/93e901a75b11a25101668a616e006b158251b16e/lib/searchkick.rb#L159-L195","documentation":"Every Searchkick knn (vector) search needs a distance metric. It is resolved from knn[:distance] or from the field's entry in the model's searchkick knn: {field: {distance: ...}} settings; if neither is present the query cannot be built and Searchkick raises ArgumentError 'distance required' in set_knn.","triggerScenarios":"Product.search('*', knn: {field: :embedding, vector: vec}) with no :distance in either place; declaring searchkick knn: {embedding: {dimensions: 384}} on the model but forgetting distance: 'cosine'.","commonSituations":"First time wiring up embeddings after adding a vector column; copy-pasting model code that omitted the distance key; passing the metric under a different key name such as metric: or similarity:.","solutions":["Set distance in the model mapping so every query inherits it: searchkick knn: {embedding: {dimensions: 384, distance: 'cosine'}} (a reindex is needed for the mapping)","Or pass it per query: Product.search('*', knn: {field: :embedding, vector: vec, distance: 'cosine'})","Use a supported value: 'cosine', 'euclidean', 'taxicab', 'inner_product' (plus 'chebyshev' only for exact OpenSearch)"],"exampleFix":"# before\nsearchkick knn: {embedding: {dimensions: 384}}\nProduct.search('*', knn: {field: :embedding, vector: vec})\n# => ArgumentError: distance required\n\n# after\nsearchkick knn: {embedding: {dimensions: 384, distance: 'cosine'}}\nProduct.search('*', knn: {field: :embedding, vector: vec})","handlingStrategy":"validation","validationCode":"def knn_query(vector, field: :embedding)\n  distance = Product.searchkick_options.dig(:knn, field)&.fetch(:distance, nil) || 'cosine'\n  Product.search('*', knn: {field: field, vector: vector, distance: distance})\nend","typeGuard":null,"tryCatchPattern":"begin\n  Product.search('*', knn: opts)\nrescue ArgumentError => e\n  raise if e.message != 'distance required'\n  opts = opts.merge(distance: 'cosine')\n  Product.search('*', knn: opts)\nend","preventionTips":["Always declare distance in the model's searchkick knn mapping so per-query omission is safe","Wrap query building in a helper that defaults the metric","Fail fast at boot: assert every knn field in searchkick options declares distance and dimensions"],"tags":["searchkick","knn","vector-search","distance-metric","argumenterror"],"backgroundTag":"missing-required-argument","analyzedSha":"93e901a75b11a25101668a616e006b158251b16e","analyzedAt":"2026-08-21T19:06:23.767Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}