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}
elseView on GitHub (pinned to 93e901a75b)
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.
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 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.
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
- Safety check failed - only run one Model.reindex per model a
- Unknown stemmer: #{stemmer[:type]}
- Must specify a distance for OpenSearch
- unknown keywords: #{unknown_keywords.join(", ")}
- Invalid value for callbacks
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/b18096e18e511b54.
Report an issue: GitHub.