ankane/searchkick · error · ArgumentError

Must have separate conversions fields

Error message

Must have separate conversions fields

What it means

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.

Source

Thrown at lib/searchkick/index_options.rb:382

      # conversions
      Array(options[:conversions]).each do |conversions_field|
        mapping[conversions_field] = {
          type: "nested",
          properties: {
            query: {type: default_type, analyzer: "searchkick_keyword"},
            count: {type: "integer"}
          }
        }
      end

      Array(options[:conversions_v2]).each do |conversions_field|
        mapping[conversions_field] = {
          type: "rank_features"
        }
      end

      if (Array(options[:conversions_v2]).map(&:to_s) & Array(options[:conversions]).map(&:to_s)).any?
        raise ArgumentError, "Must have separate conversions fields"
      end

      mapping_options =
        [:suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :filterable]
          .to_h { |type| [type, (options[type] || []).map(&:to_s)] }

      word = options[:word] != false && (!options[:match] || options[:match] == :word)

      mapping_options[:searchable].delete("_all")

      analyzed_field_options = {type: default_type, index: true, analyzer: default_analyzer.to_s}

      mapping_options.values.flatten.uniq.each do |field|
        fields = {}

        if options.key?(:filterable) && !mapping_options[:filterable].include?(field)
          fields[field] = {type: default_type, index: false}
        else

View on GitHub (pinned to 93e901a75b)

Solutions

  1. Give the v2 field its own name: `searchkick conversions_v2: ["conversions_v2"]` and store rank-feature data in that attribute.
  2. Or fully switch: remove `conversions:` and keep only `conversions_v2:` on the existing field name.
  3. 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.
  4. Run `Model.reindex` after fixing so the new mapping is applied.

Example fix

# before
class Product < ApplicationRecord
  searchkick conversions: ["conversions"], conversions_v2: ["conversions"]
end

# after
class Product < ApplicationRecord
  searchkick conversions_v2: ["conversions_v2"]
end
Defensive patterns

Strategy: validation

Validate before calling

conv_v2 = %w[conversions_v2]
legacy = %w[conversions]
raise ArgumentError, "conversions and conversions_v2 fields must not overlap" unless (conv_v2 & legacy).empty?
class Product < ApplicationRecord
  searchkick conversions: legacy, conversions_v2: conv_v2
end

Type guard

def conversions_fields_disjoint?(legacy, v2)
  (Array(legacy).map(&:to_s) & Array(v2).map(&:to_s)).empty?
end

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


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