Commit 01de136583eda91799d59da59ea14bdcdea14f33

Authored by Andrew Kane
1 parent d5312b89
Exists in conversions_v2

Added conversions_v2 option

@@ -669,7 +669,7 @@ Add conversion data with: @@ -669,7 +669,7 @@ Add conversion data with:
669 class Product < ApplicationRecord 669 class Product < ApplicationRecord
670 has_many :searches, class_name: "Searchjoy::Search", as: :convertable 670 has_many :searches, class_name: "Searchjoy::Search", as: :convertable
671 671
672 - searchkick conversions: [:conversions] # name of field 672 + searchkick conversions_v2: [:conversions] # name of field
673 673
674 def search_data 674 def search_data
675 { 675 {
@@ -2101,6 +2101,38 @@ And there’s a [new option](#default-scopes) for models with default scopes. @@ -2101,6 +2101,38 @@ And there’s a [new option](#default-scopes) for models with default scopes.
2101 2101
2102 Check out the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md#500-2022-02-21) for the full list of changes. 2102 Check out the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md#500-2022-02-21) for the full list of changes.
2103 2103
  2104 +## Upgrading Conversions
  2105 +
  2106 +Upgrade conversions without downtime. Add `conversions_v2` to your model and an additional field to `search_data`:
  2107 +
  2108 +```ruby
  2109 +class Product < ApplicationRecord
  2110 + searchkick conversions: [:conversions], conversions_v2: [:conversions_v2]
  2111 +
  2112 + def search_data
  2113 + conversions = searches.group(:query).distinct.count(:user_id)
  2114 + {
  2115 + conversions: conversions,
  2116 + conversions_v2: conversions
  2117 + }
  2118 + end
  2119 +end
  2120 +```
  2121 +
  2122 +Reindex, then remove `conversions`:
  2123 +
  2124 +```ruby
  2125 +class Product < ApplicationRecord
  2126 + searchkick conversions_v2: [:conversions_v2]
  2127 +
  2128 + def search_data
  2129 + {
  2130 + conversions_v2: searches.group(:query).distinct.count(:user_id)
  2131 + }
  2132 + end
  2133 +end
  2134 +```
  2135 +
2104 ## History 2136 ## History
2105 2137
2106 View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md). 2138 View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md).
lib/searchkick/index.rb
@@ -291,6 +291,11 @@ module Searchkick @@ -291,6 +291,11 @@ module Searchkick
291 end 291 end
292 292
293 # private 293 # private
  294 + def conversions_v2_fields
  295 + @conversions_v2_fields ||= Array(options[:conversions_v2]).map(&:to_s)
  296 + end
  297 +
  298 + # private
294 def suggest_fields 299 def suggest_fields
295 @suggest_fields ||= Array(options[:suggest]).map(&:to_s) 300 @suggest_fields ||= Array(options[:suggest]).map(&:to_s)
296 end 301 end
lib/searchkick/index_options.rb
@@ -357,6 +357,16 @@ module Searchkick @@ -357,6 +357,16 @@ module Searchkick
357 } 357 }
358 end 358 end
359 359
  360 + Array(options[:conversions_v2]).each do |conversions_field|
  361 + mapping[conversions_field] = {
  362 + type: "rank_features"
  363 + }
  364 + end
  365 +
  366 + if (Array(options[:conversions_v2]).map(&:to_s) & Array(options[:conversions]).map(&:to_s)).any?
  367 + raise ArgumentError, "Must have separate conversions fields"
  368 + end
  369 +
360 mapping_options = 370 mapping_options =
361 [:suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :filterable] 371 [:suggest, :word, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight, :searchable, :filterable]
362 .to_h { |type| [type, (options[type] || []).map(&:to_s)] } 372 .to_h { |type| [type, (options[type] || []).map(&:to_s)] }
lib/searchkick/model.rb
@@ -3,7 +3,7 @@ module Searchkick @@ -3,7 +3,7 @@ module Searchkick
3 def searchkick(**options) 3 def searchkick(**options)
4 options = Searchkick.model_options.merge(options) 4 options = Searchkick.model_options.merge(options)
5 5
6 - unknown_keywords = options.keys - [:_all, :_type, :batch_size, :callbacks, :case_sensitive, :conversions, :deep_paging, :default_fields, 6 + unknown_keywords = options.keys - [:_all, :_type, :batch_size, :callbacks, :case_sensitive, :conversions, :conversions_v2, :deep_paging, :default_fields,
7 :filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language, 7 :filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language,
8 :locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity, 8 :locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity,
9 :special_characters, :stem, :stemmer, :stem_conversions, :stem_exclusion, :stemmer_override, :suggest, :synonyms, :text_end, 9 :special_characters, :stem, :stemmer, :stem_conversions, :stem_exclusion, :stemmer_override, :suggest, :synonyms, :text_end,
lib/searchkick/query.rb
@@ -18,7 +18,7 @@ module Searchkick @@ -18,7 +18,7 @@ module Searchkick
18 18
19 def initialize(klass, term = "*", **options) 19 def initialize(klass, term = "*", **options)
20 unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost, 20 unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost,
21 - :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :explain, 21 + :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_v2, :conversions_term, :debug, :emoji, :exclude, :explain,
22 :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load, 22 :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load,
23 :match, :misspellings, :models, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile, 23 :match, :misspellings, :models, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile,
24 :request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where] 24 :request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where]
@@ -431,6 +431,7 @@ module Searchkick @@ -431,6 +431,7 @@ module Searchkick
431 } 431 }
432 432
433 should.concat(set_conversions) 433 should.concat(set_conversions)
  434 + should.concat(set_conversions_v2)
434 end 435 end
435 436
436 query = payload 437 query = payload
@@ -642,6 +643,25 @@ module Searchkick @@ -642,6 +643,25 @@ module Searchkick
642 end 643 end
643 end 644 end
644 645
  646 + def set_conversions_v2
  647 + conversions_fields = Array(options[:conversions_v2] || searchkick_options[:conversions_v2]).map(&:to_s)
  648 + # disable if searchkick_options[:conversions] to make it easy to upgrade without downtime
  649 + if conversions_fields.present? && options[:conversions_v2] != false && !(options[:conversions_v2].nil? && searchkick_options[:conversions])
  650 + conversions_fields.map do |conversions_field|
  651 + {
  652 + rank_feature: {
  653 + field: "#{conversions_field}.#{options[:conversions_term] || term}",
  654 + log: {
  655 + scaling_factor: 1
  656 + }
  657 + }
  658 + }
  659 + end
  660 + else
  661 + []
  662 + end
  663 + end
  664 +
645 def set_exclude(field, analyzer) 665 def set_exclude(field, analyzer)
646 Array(options[:exclude]).map do |phrase| 666 Array(options[:exclude]).map do |phrase|
647 { 667 {
lib/searchkick/record_data.rb
@@ -58,6 +58,12 @@ module Searchkick @@ -58,6 +58,12 @@ module Searchkick
58 end 58 end
59 end 59 end
60 60
  61 + index.conversions_v2_fields.each do |conversions_field|
  62 + unless source[conversions_field] || source[conversions_field.to_sym]
  63 + source[conversions_field] ||= {}
  64 + end
  65 + end
  66 +
61 # hack to prevent generator field doesn't exist error 67 # hack to prevent generator field doesn't exist error
62 if !partial_reindex 68 if !partial_reindex
63 index.suggest_fields.each do |field| 69 index.suggest_fields.each do |field|
test/conversions_test.rb
@@ -16,6 +16,15 @@ class ConversionsTest &lt; Minitest::Test @@ -16,6 +16,15 @@ class ConversionsTest &lt; Minitest::Test
16 assert_equal_scores "tomato", conversions: false 16 assert_equal_scores "tomato", conversions: false
17 end 17 end
18 18
  19 + def test_conversions_v2
  20 + store [
  21 + {name: "Tomato A", conversions_v2: {"tomato" => 1}},
  22 + {name: "Tomato B", conversions_v2: {"tomato" => 2}},
  23 + {name: "Tomato C", conversions_v2: {"tomato" => 3}}
  24 + ]
  25 + assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], conversions_v2: [:conversions_v2]
  26 + end
  27 +
19 def test_multiple_conversions 28 def test_multiple_conversions
20 store [ 29 store [
21 {name: "Speaker A", conversions_a: {"speaker" => 1}, conversions_b: {"speaker" => 6}}, 30 {name: "Speaker A", conversions_a: {"speaker" => 1}, conversions_b: {"speaker" => 6}},
test/models/product.rb
@@ -10,6 +10,7 @@ class Product @@ -10,6 +10,7 @@ class Product
10 ], 10 ],
11 suggest: [:name, :color], 11 suggest: [:name, :color],
12 conversions: [:conversions], 12 conversions: [:conversions],
  13 + conversions_v2: [:conversions_v2],
13 locations: [:location, :multiple_locations], 14 locations: [:location, :multiple_locations],
14 text_start: [:name], 15 text_start: [:name],
15 text_middle: [:name], 16 text_middle: [:name],
@@ -22,7 +23,7 @@ class Product @@ -22,7 +23,7 @@ class Product
22 similarity: "BM25", 23 similarity: "BM25",
23 match: ENV["MATCH"] ? ENV["MATCH"].to_sym : nil 24 match: ENV["MATCH"] ? ENV["MATCH"].to_sym : nil
24 25
25 - attr_accessor :conversions, :user_ids, :aisle, :details 26 + attr_accessor :conversions, :conversions_v2, :user_ids, :aisle, :details
26 27
27 class << self 28 class << self
28 attr_accessor :dynamic_data 29 attr_accessor :dynamic_data
@@ -33,6 +34,7 @@ class Product @@ -33,6 +34,7 @@ class Product
33 34
34 serializable_hash.except("id", "_id").merge( 35 serializable_hash.except("id", "_id").merge(
35 conversions: conversions, 36 conversions: conversions,
  37 + conversions_v2: conversions_v2,
36 user_ids: user_ids, 38 user_ids: user_ids,
37 location: {lat: latitude, lon: longitude}, 39 location: {lat: latitude, lon: longitude},
38 multiple_locations: [{lat: latitude, lon: longitude}, {lat: 0, lon: 0}], 40 multiple_locations: [{lat: latitude, lon: longitude}, {lat: 0, lon: 0}],