{"record":{"id":"b18096e18e511b54","repo":"ankane/searchkick","slug":"must-have-separate-conversions-fields","errorCode":null,"errorMessage":"Must have separate conversions fields","messagePattern":"Must have separate conversions fields","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/searchkick/index_options.rb","lineNumber":382,"sourceCode":"      # conversions\n      Array(options[:conversions]).each do |conversions_field|\n        mapping[conversions_field] = {\n          type: \"nested\",\n          properties: {\n            query: {type: default_type, analyzer: \"searchkick_keyword\"},\n            count: {type: \"integer\"}\n          }\n        }\n      end\n\n      Array(options[:conversions_v2]).each do |conversions_field|\n        mapping[conversions_field] = {\n          type: \"rank_features\"\n        }\n      end\n\n      if (Array(options[:conversions_v2]).map(&:to_s) & Array(options[:conversions]).map(&:to_s)).any?\n        raise ArgumentError, \"Must have separate conversions fields\"\n      end\n\n      mapping_options =\n        [:suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :filterable]\n          .to_h { |type| [type, (options[type] || []).map(&:to_s)] }\n\n      word = options[:word] != false && (!options[:match] || options[:match] == :word)\n\n      mapping_options[:searchable].delete(\"_all\")\n\n      analyzed_field_options = {type: default_type, index: true, analyzer: default_analyzer.to_s}\n\n      mapping_options.values.flatten.uniq.each do |field|\n        fields = {}\n\n        if options.key?(:filterable) && !mapping_options[:filterable].include?(field)\n          fields[field] = {type: default_type, index: false}\n        else","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/ankane/searchkick/blob/93e901a75b11a25101668a616e006b158251b16e/lib/searchkick/index_options.rb#L364-L400","documentation":"Searchkick supports two conversion tracking mechanisms: legacy `conversions` (nested object mapping) and `conversions_v2` (rank_features fields). This check compares the two lists as strings and raises `ArgumentError` if any field name appears in both, because the rank_features mapping it assigns to `conversions_v2` fields is incompatible with the legacy conversion field mapping.","triggerScenarios":"`searchkick conversions: [\"conversions\"], conversions_v2: [\"conversions\"]` or any spelling where the stringified names overlap, e.g. `conversions: [:conv]` plus `conversions_v2: [\"conv\"]` (symbols are compared via `map(&:to_s)`), followed by reindex/any index-options build.","commonSituations":"Migrating from legacy conversions to conversions_v2 by adding the new option next to the old one and reusing the same field name; copy-pasting an example model while keeping both options; using a symbol in one list and a string in the other and assuming they are distinct.","solutions":["Give the v2 field its own name: `searchkick conversions_v2: [\"conversions_v2\"]` and store rank-feature data in that attribute.","Or fully switch: remove `conversions:` and keep only `conversions_v2:` on the existing field name.","Rename the legacy field to something like `conversions_v1` (searchkick also supports the explicit `conversions_v1:` alias, which it maps back to `conversions`) so the two lists no longer intersect.","Run `Model.reindex` after fixing so the new mapping is applied."],"exampleFix":"# before\nclass Product < ApplicationRecord\n  searchkick conversions: [\"conversions\"], conversions_v2: [\"conversions\"]\nend\n\n# after\nclass Product < ApplicationRecord\n  searchkick conversions_v2: [\"conversions_v2\"]\nend","handlingStrategy":"validation","validationCode":"conv_v2 = %w[conversions_v2]\nlegacy = %w[conversions]\nraise ArgumentError, \"conversions and conversions_v2 fields must not overlap\" unless (conv_v2 & legacy).empty?\nclass Product < ApplicationRecord\n  searchkick conversions: legacy, conversions_v2: conv_v2\nend","typeGuard":"def conversions_fields_disjoint?(legacy, v2)\n  (Array(legacy).map(&:to_s) & Array(v2).map(&:to_s)).empty?\nend","tryCatchPattern":null,"preventionTips":["When migrating to conversions_v2, delete the legacy option in the same commit instead of keeping both.","Treat symbol vs string field names as identical — searchkick compares their to_s forms.","Reindex immediately after any conversions option change."],"tags":["searchkick","conversions","rank-features","mapping","configuration"],"backgroundTag":"conflicting-options","analyzedSha":"93e901a75b11a25101668a616e006b158251b16e","analyzedAt":"2026-08-21T19:06:23.767Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}