{"record":{"id":"7cffe8e2c9bceb3c","repo":"ankane/searchkick","slug":"options-incompatible-with-body-option-ignored-o","errorCode":null,"errorMessage":"Options incompatible with body option: #{ignored_options.join(\", \")}","messagePattern":"Options incompatible with body option: #(.+?)","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/searchkick/query.rb","lineNumber":271,"sourceCode":"      max_result_window = searchkick_options[:max_result_window]\n      original_per_page = per_page\n      if max_result_window\n        offset = max_result_window if offset > max_result_window\n        per_page = max_result_window - offset if offset + per_page > max_result_window\n      end\n\n      # model and eager loading\n      load = options[:load].nil? ? true : options[:load]\n\n      all = term == \"*\"\n\n      @json = options[:body]\n      if @json\n        ignored_options = options.keys & [:aggs, :boost,\n          :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :exclude, :explain,\n          :fields, :highlight, :indices_boost, :match, :misspellings, :operator, :order,\n          :profile, :select, :smart_aggs, :suggest, :where]\n        raise ArgumentError, \"Options incompatible with body option: #{ignored_options.join(\", \")}\" if ignored_options.any?\n        payload = @json\n      else\n        must_not = []\n        should = []\n\n        if options[:similar]\n          like = options[:similar] == true ? term : options[:similar]\n          query = {\n            more_like_this: {\n              like: like,\n              min_doc_freq: 1,\n              min_term_freq: 1,\n              analyzer: \"searchkick_search2\"\n            }\n          }\n          if fields.all? { |f| f.start_with?(\"*.\") }\n            raise ArgumentError, \"Must specify fields to search\"\n          end","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/ankane/searchkick/blob/93e901a75b11a25101668a616e006b158251b16e/lib/searchkick/query.rb#L253-L289","documentation":"`body:` replaces the entire query payload searchkick would generate, so query-shaping options would be silently ignored — for example `where`, `order`, `aggs`, `boost_by`, `misspellings`, `fields`, `operator`, `highlight`. To prevent silent no-ops, `Query#prepare` intersects your option keys with that list and raises `ArgumentError` listing any incompatible ones when `body:` is present.","triggerScenarios":"`Product.search(\"milk\", body: {query: {...}}, where: {in_stock: true}, order: {price: :asc})` — the `where`/`order` keys trip the check. Also passing `fields:` or `misspellings:` alongside a hand-written body, or wrapping an existing search call with `body:` while leaving old options in place.","commonSituations":"Graduating from option-based queries to raw ES DSL for one complex query and forgetting to remove the now-dead options; merging code where a `body:` branch is added around an option-heavy call; copy-pasting a raw query from Kibana/Curl into `.search(body: ...)` while keeping surrounding options.","solutions":["Remove the listed options and translate their intent into the raw body DSL (bool filter for `where`, sort for `order`, aggs for `aggs`).","Keep the parts that are compatible — `load`, `includes`, `page/per_page/limit/offset`, `index_name`, `models`, `request_params`, `scroll` are not on the forbidden list and still work with `body:`.","If you need both generated and raw pieces, use `body_options:` (merged into the generated payload) instead of `body:`.","Write the body in a named scope/method so the search call stays option-free and greppable."],"exampleFix":"# before\nProduct.search(\"milk\",\n  body: {query: {match_all: {}}, sort: [{price: \"asc\"}]},\n  where: {in_stock: true}, order: {price: :asc}\n) # => ArgumentError: Options incompatible with body option: order, where\n\n# after\nProduct.search(nil,\n  body: {\n    query: {bool: {must: {match_all: {}}, filter: {term: {\"in_stock\": true}}}},\n    sort: [{price: \"asc\"}]\n  }\n)","handlingStrategy":"validation","validationCode":"FORBIDDEN_WITH_BODY = %i[aggs boost boost_by boost_by_distance boost_by_recency boost_where conversions conversions_term exclude explain fields highlight indices_boost match misspellings operator order profile select smart_aggs suggest where].freeze\n\ndef raw_search(term, body:, **opts)\n  bad = opts.keys & FORBIDDEN_WITH_BODY\n  raise ArgumentError, \"not allowed with body: #{bad.join(', ')}\" if bad.any?\n  Product.search(term, body: body, **opts)\nend","typeGuard":"def body_safe_options?(opts)\n  forbidden = %i[aggs boost boost_by boost_by_distance boost_by_recency boost_where conversions conversions_term exclude explain fields highlight indices_boost match misspellings operator order profile select smart_aggs suggest where]\n  (opts.keys & forbidden).empty?\nend","tryCatchPattern":null,"preventionTips":["When adopting `body:`, delete option-based query shaping from that call in the same edit — don't leave dead options.","Use `body_options:` when you only need to merge extra payload keys into a generated query.","Remember `load`, `page/per_page`, `includes`, `index_name` remain valid alongside `body:`."],"tags":["searchkick","body-option","raw-query","argumenterror"],"backgroundTag":"conflicting-options","analyzedSha":"93e901a75b11a25101668a616e006b158251b16e","analyzedAt":"2026-08-21T19:06:23.767Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}